diff --git a/pom.xml b/pom.xml
index ac599310..fd572fdc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
xxl-job-core
xxl-job-admin
+ xxl-job-spring-boot-starter
xxl-job-executor-samples
diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml
index 9782a28d..bebeccfc 100644
--- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml
+++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/pom.xml
@@ -43,10 +43,10 @@
test
-
+
com.xuxueli
- xxl-job-core
+ xxl-job-spring-boot-starter
${project.parent.version}
diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java
deleted file mode 100644
index bfd80e22..00000000
--- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.xxl.job.executor.core.config;
-
-import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * xxl-job config
- *
- * @author xuxueli 2017-04-28
- */
-@Configuration
-public class XxlJobConfig {
- private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
-
- @Value("${xxl.job.admin.addresses}")
- private String adminAddresses;
-
- @Value("${xxl.job.accessToken}")
- private String accessToken;
-
- @Value("${xxl.job.executor.appname}")
- private String appname;
-
- @Value("${xxl.job.executor.address}")
- private String address;
-
- @Value("${xxl.job.executor.ip}")
- private String ip;
-
- @Value("${xxl.job.executor.port}")
- private int port;
-
- @Value("${xxl.job.executor.logpath}")
- private String logPath;
-
- @Value("${xxl.job.executor.logretentiondays}")
- private int logRetentionDays;
-
-
- @Bean
- public XxlJobSpringExecutor xxlJobExecutor() {
- logger.info(">>>>>>>>>>> xxl-job config init.");
- XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
- xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
- xxlJobSpringExecutor.setAppname(appname);
- xxlJobSpringExecutor.setAddress(address);
- xxlJobSpringExecutor.setIp(ip);
- xxlJobSpringExecutor.setPort(port);
- xxlJobSpringExecutor.setAccessToken(accessToken);
- xxlJobSpringExecutor.setLogPath(logPath);
- xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
-
- return xxlJobSpringExecutor;
- }
-
- /**
- * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
- *
- * 1、引入依赖:
- *
- * org.springframework.cloud
- * spring-cloud-commons
- * ${version}
- *
- *
- * 2、配置文件,或者容器启动变量
- * spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
- *
- * 3、获取IP
- * String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
- */
-
-
-}
\ No newline at end of file
diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties
index e067db4f..f718d2d8 100644
--- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties
+++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties
@@ -1,26 +1,9 @@
# web port
server.port=8081
-# no web
-#spring.main.web-environment=false
-# log config
-logging.config=classpath:logback.xml
+spring.application.name=demo-app
-### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
-xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
+xxl-job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin,http://192.168.50.11:8080/xxl-job-admin
+xxl-job.executor.preferred-networks=192.168.50
-### xxl-job, access token
-xxl.job.accessToken=
-
-### xxl-job executor appname
-xxl.job.executor.appname=xxl-job-executor-sample
-### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
-xxl.job.executor.address=
-### xxl-job executor server-info
-xxl.job.executor.ip=
-xxl.job.executor.port=9999
-### xxl-job executor log-path
-xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
-### xxl-job executor log-retention-days
-xxl.job.executor.logretentiondays=30
diff --git a/xxl-job-spring-boot-starter/README.md b/xxl-job-spring-boot-starter/README.md
new file mode 100644
index 00000000..91cbec1e
--- /dev/null
+++ b/xxl-job-spring-boot-starter/README.md
@@ -0,0 +1,57 @@
+
+# 使用方式
+## 1. 添加依赖
+```xml
+
+ com.xuxueli
+ xxl-job-spring-boot-starter
+ 2.3.0
+
+```
+
+## 2. 配置 application.yml
+遵循约定优于配置的原则, 提供更多的默认配置
+最低配置仅需提供 admin.address 即可
+```yaml
+spring:
+ application:
+ name: demo-app
+
+xxl-job:
+ admin:
+ addresses: http://localhost:7878 # 必填
+ access-token: abcdefghijklmn # 可选, 若 amin 配有 accessToken, 则需要填写
+ executor:
+ enable: true # 是否启用, 默认: true
+ app-name: demo-app # 可选, 为空则取值 ${spring.application.name}
+ port-min: 30000 # 可选, 默认: 30000
+ port-max: 40000 # 可选, 默认: 49151
+ ip: 192.168.3.5 # 可选, 为空则按 preferred-networks 匹配
+ preferred-networks: # 可选, 当 ip 为空时, 将遍历网卡的 ip 与此值进行 startWith 匹配
+ - 192.168.50
+```
+### 关于 ip 相关配置
+若已经配置 ip, 则以配置为准
+若 ip 留空, 则按 preferred-networks 进行匹配
+若 ip 与 preferred-networks 均未提供, 则按 xxl-job-core 的 IpUtil 获取
+
+警告:
+多网卡环境, 采用 IpUtil 获取本机 ip 进行注册, 可能存在问题.
+比如, admin 与 job 不在同一台机器, 此时使用 job 获取的本机 ip 注册到 admin, 但 admin 可能无法访问此 ip!!!
+
+建议:
+手工指定本机 ip, 或者配置 preferred-networks, 以确保 admin 能正常访问此 ip
+
+
+### 关于 port 相关配置
+port-max 和 port-min 分别指端口的最大值和最小值
+启动时, 会在区间内自动查找可用端口.
+查找方法: 由最大值开始, 一直递减, 直到找到可用为止.
+若此区间内无可用端口, 则会抛出异常, 无法启动
+
+通常情况下, 建议使用默认的区间值
+未提供特定端口的配置, 若希望固定端口, 请将 port-min 与 port-max 设为同一值
+
+
+## 3. 其他
+照常编写 @XxlJob 之类的任务方法即可
diff --git a/xxl-job-spring-boot-starter/pom.xml b/xxl-job-spring-boot-starter/pom.xml
new file mode 100644
index 00000000..271818d8
--- /dev/null
+++ b/xxl-job-spring-boot-starter/pom.xml
@@ -0,0 +1,65 @@
+
+ 4.0.0
+
+ com.xuxueli
+ xxl-job
+ 2.3.0
+
+ xxl-job-spring-boot-starter
+ jar
+
+ ${project.artifactId}
+ xxl-job spring-boot starter
+ https://www.xuxueli.com/
+
+
+
+ com.xuxueli
+ xxl-job-core
+ ${project.parent.version}
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ compile
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ org.slf4j
+ slf4j-api
+ compile
+
+
+
+
+ junit
+ junit
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
\ No newline at end of file
diff --git a/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobAdminProperties.java b/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobAdminProperties.java
new file mode 100644
index 00000000..30c6b41b
--- /dev/null
+++ b/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobAdminProperties.java
@@ -0,0 +1,31 @@
+package com.xxl.job.spring.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * admin config
+ * @author fanhang
+ */
+@Configuration
+@ConfigurationProperties(prefix = "xxl-job.admin")
+public class XxlJobAdminProperties {
+ private String addresses;
+ private String accessToken;
+
+ public String getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(String addresses) {
+ this.addresses = addresses;
+ }
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public void setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ }
+}
\ No newline at end of file
diff --git a/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobAutoConfiguration.java b/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobAutoConfiguration.java
new file mode 100644
index 00000000..c2ebeb22
--- /dev/null
+++ b/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobAutoConfiguration.java
@@ -0,0 +1,107 @@
+package com.xxl.job.spring.config;
+
+import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
+import com.xxl.job.core.util.IpUtil;
+import com.xxl.job.core.util.NetUtil;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.Enumeration;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.*;
+
+/**
+ * @author fanhang
+ */
+@Configuration
+public class XxlJobAutoConfiguration {
+ private static final Logger log = LoggerFactory.getLogger(XxlJobAutoConfiguration.class);
+
+ @Bean
+ @ConditionalOnProperty(name = "xxl-job.executor.enable", havingValue = "true", matchIfMissing = true)
+ public XxlJobSpringExecutor xxlJobSpringExecutor(XxlJobAdminProperties adminProps, XxlJobExecutorProperties executorProps) {
+ log.info("xxl-job admin [{}] with properties: {}", adminProps.getAddresses(), executorProps);
+ Assert.hasText(adminProps.getAddresses(), "require [xxl-job.admin.addresses]");
+ Assert.hasText(executorProps.getAppName(), "require [xxl-job.executor.appName]");
+ String ip = resolveIp(executorProps.getIp(), executorProps.getPreferredNetworks());
+ Assert.hasLength(ip, "resolve ip failed");
+ int port = resolvePort(executorProps.getPortMin(), executorProps.getPortMax());
+
+ XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
+ xxlJobSpringExecutor.setAdminAddresses(adminProps.getAddresses());
+ if (StringUtils.hasLength(adminProps.getAccessToken())) {
+ xxlJobSpringExecutor.setAccessToken(adminProps.getAccessToken());
+ }
+ xxlJobSpringExecutor.setAppname(executorProps.getAppName());
+ xxlJobSpringExecutor.setPort(port);
+ xxlJobSpringExecutor.setLogPath(executorProps.getLogPath());
+ xxlJobSpringExecutor.setLogRetentionDays(executorProps.getLogRetentionDays());
+ xxlJobSpringExecutor.setIp(ip);
+ log.info("xxl-job-executor: (admin:{}, appName:{}, ip={}, port:{})", adminProps.getAddresses(), executorProps.getAppName(), ip, port);
+ return xxlJobSpringExecutor;
+ }
+
+ private String resolveIp(String ip, Set preferredNetworks) {
+ if (StringUtils.hasLength(ip)) {
+ // 已设置 ip
+ return ip;
+ }
+ // 未设置 ip
+ if (!CollectionUtils.isEmpty(preferredNetworks)) {
+ // 已配置 preferredNetworks
+ ip = findNonLoopbackPreferredAddress(preferredNetworks);
+ if (StringUtils.hasLength(ip)) {
+ return ip;
+ }
+ }
+ // 始终都没有找到 ip, 则按 xxl-job-core 默认方式获取
+ ip = IpUtil.getIp();
+ log.warn("auto get address: {}", ip);
+ return ip;
+ }
+
+ private int resolvePort(int portMin, int portMax) {
+ Assert.state(portMin <= portMax, String.format("invalid port range [%d, %d]", portMin, portMax));
+ int port = NetUtil.findAvailablePort(portMax);
+ Assert.state(port >= portMin, String.format("find available port [%d] out of bounds: [%d, %d]", port, portMin, portMax));
+ return port;
+ }
+
+ /**
+ * copy from spring-cloud-commons #InetUtils
+ *
+ * @param preferredNetworks
+ * @return
+ */
+ private String findNonLoopbackPreferredAddress(Set preferredNetworks) {
+ try {
+ for (Enumeration nics = NetworkInterface.getNetworkInterfaces(); nics.hasMoreElements(); ) {
+ NetworkInterface ifc = nics.nextElement();
+ if (ifc.isLoopback() || ifc.isVirtual() || !ifc.isUp()) {
+ continue;
+ }
+ for (Enumeration addrs = ifc.getInetAddresses(); addrs.hasMoreElements(); ) {
+ InetAddress address = addrs.nextElement();
+ if (address.isLoopbackAddress()) {
+ continue;
+ }
+ for (String preferredNetwork : preferredNetworks) {
+ if (address.getHostAddress().startsWith(preferredNetwork)) {
+ log.debug("resolved preferred ip [{}] from [{} - {}]", address.getHostAddress(), ifc.getName(), ifc.getDisplayName());
+ return address.getHostAddress();
+ }
+ }
+ }
+ }
+ } catch (IOException ex) {
+ log.error("Cannot get non-loopback address", ex);
+ }
+ return null;
+ }
+}
diff --git a/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobExecutorProperties.java b/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobExecutorProperties.java
new file mode 100644
index 00000000..2a497610
--- /dev/null
+++ b/xxl-job-spring-boot-starter/src/main/java/com/xxl/job/spring/config/XxlJobExecutorProperties.java
@@ -0,0 +1,150 @@
+package com.xxl.job.spring.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.util.StringUtils;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * executor config
+ * @author fanhang
+ */
+@Configuration
+@ConfigurationProperties(prefix = "xxl-job.executor")
+public class XxlJobExecutorProperties implements EnvironmentAware, InitializingBean {
+ private static final Logger log = LoggerFactory.getLogger(XxlJobExecutorProperties.class);
+ private static final int PORT_MIN = 30000;
+ private static final int PORT_MAX = 49151;
+
+ private Environment environment;
+
+ private boolean enable = true;
+ /**
+ * executor appName, default ${spring.application.name}
+ */
+ private String appName;
+ /**
+ * executor ip
+ */
+ private String ip;
+ /**
+ * logPath dir, default ${user.home}/logs/xxl-job/${spring.application.name}
+ */
+ private String logPath;
+ private int logRetentionDays = 30;
+ /**
+ * port range min
+ */
+ private int portMin = PORT_MIN;
+ /**
+ * port range max
+ */
+ private int portMax = PORT_MAX;
+ private Set preferredNetworks;
+
+ public boolean isEnable() {
+ return enable;
+ }
+
+ public void setEnable(boolean enable) {
+ this.enable = enable;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ public String getIp() {
+ return ip;
+ }
+
+ public void setIp(String ip) {
+ this.ip = ip;
+ }
+
+ public String getLogPath() {
+ return logPath;
+ }
+
+ public void setLogPath(String logPath) {
+ this.logPath = logPath;
+ }
+
+ public int getLogRetentionDays() {
+ return logRetentionDays;
+ }
+
+ public void setLogRetentionDays(int logRetentionDays) {
+ this.logRetentionDays = logRetentionDays;
+ }
+
+ public int getPortMin() {
+ return portMin;
+ }
+
+ public void setPortMin(int portMin) {
+ this.portMin = portMin;
+ }
+
+ public int getPortMax() {
+ return portMax;
+ }
+
+ public void setPortMax(int portMax) {
+ this.portMax = portMax;
+ }
+
+ public Set getPreferredNetworks() {
+ return preferredNetworks;
+ }
+
+ public void setPreferredNetworks(Set preferredNetworks) {
+ this.preferredNetworks = preferredNetworks;
+ }
+
+ @Override
+ public void setEnvironment(Environment environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ if (!enable) {
+ log.warn("xxl-job-executor DISABLE because [xxl-job.executor.enable] is false ");
+ return;
+ }
+ if (!StringUtils.hasLength(appName)) {
+ appName = environment.getProperty("spring.application.name");
+ }
+ if (!StringUtils.hasLength(logPath)) {
+ String userHome = environment.getProperty("user.home");
+ logPath = userHome + File.separator + "logs" + File.separator + "xxl-job" + File.separator + appName;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "XxlJobProperties{" +
+ "enable=" + enable +
+ ", appName='" + appName + '\'' +
+ ", ip='" + ip + '\'' +
+ ", logPath='" + logPath + '\'' +
+ ", logRetentionDays=" + logRetentionDays +
+ ", portMin=" + portMin +
+ ", portMax=" + portMax +
+ ", preferredNetworks=" + preferredNetworks +
+ '}';
+ }
+
+}
\ No newline at end of file
diff --git a/xxl-job-spring-boot-starter/src/main/resources/META-INF/spring.factories b/xxl-job-spring-boot-starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..f75b077a
--- /dev/null
+++ b/xxl-job-spring-boot-starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,4 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.xxl.job.spring.config.XxlJobAdminProperties,\
+com.xxl.job.spring.config.XxlJobExecutorProperties,\
+com.xxl.job.spring.config.XxlJobAutoConfiguration
diff --git a/xxl-job-spring-boot-starter/src/test/java/com/xxl/job/springtest/Boot.java b/xxl-job-spring-boot-starter/src/test/java/com/xxl/job/springtest/Boot.java
new file mode 100644
index 00000000..16494fe7
--- /dev/null
+++ b/xxl-job-spring-boot-starter/src/test/java/com/xxl/job/springtest/Boot.java
@@ -0,0 +1,13 @@
+package com.xxl.job.springtest;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Boot {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Boot.class, args);
+ }
+
+}
diff --git a/xxl-job-spring-boot-starter/src/test/resources/application.yml b/xxl-job-spring-boot-starter/src/test/resources/application.yml
new file mode 100644
index 00000000..ba6074bf
--- /dev/null
+++ b/xxl-job-spring-boot-starter/src/test/resources/application.yml
@@ -0,0 +1,15 @@
+spring:
+ application:
+ name: demo-app
+
+xxl-job:
+ admin:
+ addresses: http://localhost:8080/xxl-job-admin
+ executor:
+ preferred-networks:
+ - 192.168.50
+
+
+logging:
+ level:
+ com.xxl.job: debug