自定义Starter

自定义Starter:myQuartz-spring-boot-starter

模仿学习Spring Boot starter写一个关于Quartz的自动配置的依赖。

1. 自定义Starter的模块结构

模块结构.png

2. Maven 依赖配置

自定义starter命名方式:

  • 官方 spring-boot-starter-模块名
  • 非官方(如我们自己编写的) 模块名-spring-boot-starter

spring-boot-configuration-processor是2.x必须引入的包。

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.littlefxc.examples</groupId>
    <artifactId>myQuartz-spring-boot-starter</artifactId>
    <version>1.0-snapshot</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.18.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <quartz.version>2.3.0</quartz.version>
    </properties>

    <dependencies>
        <!-- starter的基本配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- quartz的基本配置 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>${quartz.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>c3p0</artifactId>
                    <groupId>com.mchange</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>HikariCP-java6</artifactId>
                    <groupId>com.zaxxer</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
    </dependencies>
</project>

3. 创建自动配置类QuartzAutoConfiguration

构造函数隐式注入
在4.3之前,如果你构造函数中要依赖另外一个bean,你必须显示依赖@Autowired, 例如:

@Service
public class FooService {
    private final FooRepository repository;
    @Autowired
    public FooService(FooRepository repository) {
        this.repository = repository
    }
}

相当常见的用例但是如果你忘记构造函数上的@Autowired注释,容器将抛出一个寻找默认构造函数的异常,除非你在bean定义设置中明确指出autowire模式’constructor’。

因此,从4.3开始,您不再需要在这样的单构造函数场景中指定显式注入注释。对于那些根本不带任何容器注释的类来说,这是特别优雅的,FooService会从beanFactory中查找FooRepository。

同样的,@Configuration类在4.3之前不支持构造函数注入。

为什么要使用ObjectProvider?

上文讲过构造函数隐式注入,但其有个缺点那就是强依赖。
Spring Framework 4.3引入了ObjectProvider,它是现有ObjectFactory接口的扩展,具有方便的签名,例如getIfAvailable和getIfUnique,只有在它实际存在时才检索bean(可选支持)或者如果可以确定单个候选者(特别是:主要候选者)在多个匹配的bean的情况下)。它的Spring核心源码见org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency()

** 用到的几个和Starter相关的注解:

  • @ConditionalOnClass,当classpath下发现该类的情况下进行自动配置。
  • @ConditionalOnMissingBean,当Spring Context中不存在该Bean时。
  • @EnableConfigurationProperties(QuartzProperties.class),使@ConfigurationProperties注解生效。
  • @ConfigurationProperties,主要用来把properties配置文件转化为bean。
  • @AutoConfigureAfter,自动注入该类在什么类加载之后。

自定义Quartz的调度器工厂Bean的自动配置类

@Configuration
@ConditionalOnClass({Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class})
@EnableConfigurationProperties(QuartzProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class QuartzAutoConfiguration {

    private final static Logger log = LoggerFactory.getLogger(QuartzAutoConfiguration.class);

    private final List<SchedulerFactoryBeanCustomizer> customizers;

    private final QuartzProperties properties;

    private final JobDetail[] jobDetails;

    private final Map<String, Calendar> calendars;

    private final Trigger[] triggers;

    private final ApplicationContext applicationContext;

    private final DataSource dataSource;

    private final PlatformTransactionManager transactionManager;

    public QuartzAutoConfiguration(QuartzProperties properties,
                                   ObjectProvider<List<SchedulerFactoryBeanCustomizer>> customizers,
                                   ObjectProvider<JobDetail[]> jobDetails,
                                   ObjectProvider<Map<String, Calendar>> calendars,
                                   ObjectProvider<Trigger[]> triggers,
                                   ApplicationContext applicationContext,
                                   ObjectProvider<DataSource> dataSource,
                                   ObjectProvider<PlatformTransactionManager> transactionManager) {
        this.properties = properties;
        this.jobDetails = jobDetails.getIfAvailable();
        this.calendars = calendars.getIfAvailable();
        this.triggers = triggers.getIfAvailable();
        this.applicationContext = applicationContext;
        this.dataSource = dataSource.getIfAvailable();
        this.transactionManager = transactionManager.getIfAvailable();
        this.customizers = customizers.getIfAvailable();
    }

    /**
     * 如果没有调度器,就创建
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public SchedulerFactoryBean schedulerFactoryBean() {
        log.info("Init SchedulerFactoryBean");
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        schedulerFactoryBean.setJobFactory(new AutoSchedulerJobFactory(this.applicationContext.getAutowireCapableBeanFactory()));
        if (!this.properties.getProperties().isEmpty()) {
            schedulerFactoryBean.setQuartzProperties(asProperties(this.properties.getProperties()));
        }
        if (this.jobDetails != null && this.jobDetails.length > 0) {
            schedulerFactoryBean.setJobDetails(this.jobDetails);
        }
        if (this.calendars != null && !this.calendars.isEmpty()) {
            schedulerFactoryBean.setCalendars(this.calendars);
        }
        if (this.triggers != null && this.triggers.length > 0) {
            schedulerFactoryBean.setTriggers(this.triggers);
        }
        // todo quartz数据源初始化
        if (properties.getJobStoreType() == JobStoreType.JDBC) {
            if (dataSource != null) {
                schedulerFactoryBean.setDataSource(dataSource);
            }
            if (transactionManager != null) {
                schedulerFactoryBean.setTransactionManager(transactionManager);
            }
        }
        customize(schedulerFactoryBean);
        return schedulerFactoryBean;
    }

    /**
     * 自定义调度器
     *
     * @param schedulerFactoryBean
     */
    private void customize(SchedulerFactoryBean schedulerFactoryBean) {
        if (this.customizers != null) {
            for (SchedulerFactoryBeanCustomizer customizer : this.customizers) {
                customizer.customize(schedulerFactoryBean);
            }
        }
    }

    private Properties asProperties(Map<String, String> source) {
        Properties properties = new Properties();
        properties.putAll(source);
        return properties;
    }

}

4. resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.littlefxc.examples.spring.boot.autoconfigure.QuartzAutoConfiguration

5. 其它

QuartzProperties

@ConfigurationProperties("spring.quartz")
public class QuartzProperties {

    /**
     * Quartz job store type.
     */
    private JobStoreType jobStoreType = JobStoreType.MEMORY;

    /**
     * Additional Quartz Scheduler properties.
     */
    private final Map<String, String> properties = new HashMap<>();

    public JobStoreType getJobStoreType() {
        return this.jobStoreType;
    }

    public void setJobStoreType(JobStoreType jobStoreType) {
        this.jobStoreType = jobStoreType;
    }

    public Map<String, String> getProperties() {
        return this.properties;
    }
}

JobStoreType

public enum JobStoreType {

    /**
     * Store jobs in memory.
     */
    MEMORY,

    /**
     * Store jobs in the database.
     */
    JDBC

}

AutoSchedulerJobFactory

/**
 * 模仿了:{@link org.springframework.boot.autoconfigure.quartz.AutowireCapableBeanJobFactory}
 *
 * @author fengxuechao
 * @date 12/19/2018
 * @see <a href="http://blog.btmatthews.com/?p=40#comment-33797">注入Spring上下文(applicationContext)
 */
public class AutoSchedulerJobFactory extends SpringBeanJobFactory {

    private AutowireCapableBeanFactory beanFactory;

    AutoSchedulerJobFactory(AutowireCapableBeanFactory factory) {
        beanFactory = factory;
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        this.beanFactory.initializeBean(job, null);
        return job;
    }
}

SchedulerFactoryBeanCustomizer

/**
 * 回调接口,可以由希望在完全初始化之前自定义Quartz SchedulerFactoryBean的bean实现,特别是调整其配置。
 * @author fengxuechao
 */
@FunctionalInterface
public interface SchedulerFactoryBeanCustomizer {

    /**
     * 自定义{@link SchedulerFactoryBean}.
     * @param schedulerFactoryBean the scheduler to customize
     */
    void customize(SchedulerFactoryBean schedulerFactoryBean);

}

6. 测试

6.1 项目结构

新建项目,结构如下图所示:

test-autoconfigure项目结构.png

6.2 Maven 依赖

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.littlefxc.examples</groupId>
    <artifactId>test-autoconfigure</artifactId>
    <version>1.0-snapshot</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!--<version>2.0.4.RELEASE</version>-->
        <version>1.5.18.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <quartz.version>2.3.0</quartz.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.littlefxc.examples</groupId>
            <artifactId>myQuartz-spring-boot-starter</artifactId>
            <version>1.0-snapshot</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>${quartz.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
    </dependencies>
</project>

6.3 application.properties

logging.level.root=warn
logging.level.com.littlefxc.examples=debug

# DataSource Config
spring.datasource.url=jdbc:mysql://localhost:3306/learn-quartz?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.filters=slf4j,wall
spring.datasource.druid.initial-size=1
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-active=8
spring.datasource.druid.max-wait=60000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20

# Quartz Config
spring.quartz.job-store-type=jdbc
spring.quartz.properties.org.quartz.scheduler.instanceName=schedulerFactoryBean
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.scheduler.instanceIdGenerator.class=com.littlefxc.examples.CustomQuartzInstanceIdGenerator
spring.quartz.properties.org.quartz.threadPool.threadCount=20
spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.useProperties=true
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
spring.quartz.properties.org.quartz.jobStore.tablePrefix=qrtz_
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.plugin.shutdownHook.class=org.quartz.plugins.management.ShutdownHookPlugin
spring.quartz.properties.org.quartz.plugin.shutdownHook.cleanShutdown=TRUE

6.4 代码

核心代码

@Slf4j
@SpringBootApplication
public class TestAutoConfigure {

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

    @Bean
    public SchedulerFactoryBeanCustomizer dataSourceCustomizer() {
        return (schedulerFactoryBean) -> {
            schedulerFactoryBean.setOverwriteExistingJobs(false);
            schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
            schedulerFactoryBean.setStartupDelay(10);
        };
    }

    /**
     * JobDetailFactoryBean
     *
     * @return
     */
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean() {
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        bean.setName("job-1");
        bean.setGroup("job-group-1");
        bean.setJobClass(MyJob.class);
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("hello", "world");
        bean.setJobDataMap(jobDataMap);
        bean.setDurability(true);
        return bean;
    }

    /**
     * CronTriggerFactoryBean
     *
     * @param jobDetail
     * @return
     */
    @Bean
    public CronTriggerFactoryBean cronTrigger(JobDetail jobDetail) {
        CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
        bean.setName("cron-1");
        bean.setGroup("cron-group-1");
        bean.setCronExpression("0/5 * * * * ?");
        bean.setJobDetail(jobDetail);
        return bean;
    }

    /**
     * Job
     */
    @Component
    public static class MyJob extends QuartzJobBean {

        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            JobDetail jobDetail = jobExecutionContext.getJobDetail();
            String jobName = jobDetail.getKey().getName();
            String jobGroup = jobDetail.getKey().getGroup();
            String jobDataMapHello = (String) jobDetail.getJobDataMap().get("hello");
            log.info("job.name = {}, job.group = {}, job.dataMap.hello = {}", jobName, jobGroup, jobDataMapHello);
        }
    }
}

CustomQuartzInstanceIdGenerator

public class CustomQuartzInstanceIdGenerator implements InstanceIdGenerator {

    @Override
    public String generateInstanceId() throws SchedulerException {
        try {
            return UUID.randomUUID().toString().replaceAll("-", "");
        } catch (Exception ex) {
            throw new SchedulerException("Couldn't generate UUID!", ex);
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值