init project

This commit is contained in:
jaysunxiao committed 2021-05-20 14:17:21 +08:00
1 parent b7f28da485
commit bce060a28e
573 files changed
+84513

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
### Ⅰ. 注意事项
- 每秒钟执行一次SchedulerManager.triggerPerSecond()方法,循环遍历可执行的scheduler
- triggerPerSecond在计算下一次triggerTimestamp会很耗时间,所以尽量避免配置一秒或者间隔时间很短的cron表达式
- SchedulerManager的executor只有一条线程,所以使用者要避免做耗时和阻塞的运算,如果有这样的需求可以抛到其它线程池
- 时间可以向前调,也可以向后调,都会出发trigger
### Ⅱ. Cron Expression Example
```
30 * * * * ? 每半分钟触发任务
30 10 * * * ? 每小时的10分30秒触发任务
30 10 1 * * ? 每天1点10分30秒触发任务
30 10 1 20 * ? 每月20号1点10分30秒触发任务
30 10 1 20 10 ? * 每年10月20号1点10分30秒触发任务
30 10 1 20 10 ? 2018 2018年10月20号1点10分30秒触发任务
30 10 1 ? 10 * 2018 2018年10月每天20号1点10分30秒触发任务
30 10 1 ? 10 SUN 2018 2018年10月每周日1点10分30秒触发任务
15,30,45 * * * * ? 每分钟的15,30,45秒个触发一次
15-45 * * * * ? 每分钟的15秒到45秒内,每秒都触发一次
15/5 * * * * ? 每分钟的15秒开始触发,每隔5秒触发一次
15-30/5 * * * * ? 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
0 0/3 * * * ? 每小时的第0分0秒开始,没三分钟触发一次
0 15 10 ? * MON-FRI 星期一到星期五每天10点15分0秒触发一次
0 15 10 L * ? 每个月的最后一天的10点15分0秒触发任务
0 15 10 LW * ? 每个月最后一个工作日的10点15分0秒触发任务
0 15 10 ? * 5L 每个月最后一个星期四的10点15分0秒触发任务
0 15 10 ? * 5#3 每个月第三周的星期四的10点15分0秒触发任务
说明:
*(星号):代表任何时刻都接受癿意思
,(逗号):代表分隔时段的意思
-(减号):代表一段时间范围内
/n(斜线):每隔n单位间隔
```
+243
View File
@@ -0,0 +1,243 @@
<?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.zfoo</groupId>
<artifactId>scheduler</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<properties>
<!-- 本项目的其它module版本号 -->
<zfoo.event.version>3.0</zfoo.event.version>
<zfoo.hotswap.version>3.0</zfoo.hotswap.version>
<zfoo.monitor.version>3.0</zfoo.monitor.version>
<zfoo.net.version>3.0</zfoo.net.version>
<zfoo.scheduler.version>3.0</zfoo.scheduler.version>
<zfoo.storage.version>3.0</zfoo.storage.version>
<zfoo.orm.version>3.0</zfoo.orm.version>
<zfoo.protocol.version>3.0</zfoo.protocol.version>
<zfoo.util.version>3.0</zfoo.util.version>
<!-- 核心spring框架 -->
<spring.version>5.3.4</spring.version>
<spring.boot.version>2.4.3</spring.boot.version>
<!-- 工具包 -->
<commons-codec.version>1.15</commons-codec.version>
<commons-io.version>2.8.0</commons-io.version>
<commons-collections.version>4.4</commons-collections.version>
<commons-lang.version>3.12.0</commons-lang.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<commons-logging.version>1.2</commons-logging.version>
<commons-log4j.version>2.14.0</commons-log4j.version>
<httpcomponents.version>4.5.13</httpcomponents.version>
<httpcore.version>4.4.14</httpcore.version>
<google.guava.version>30.1-jre</google.guava.version>
<google.protobuf.version>3.9.1</google.protobuf.version>
<google.gson.version>2.8.6</google.gson.version>
<kryo.version>5.0.3</kryo.version>
<caffeine.version>2.8.8</caffeine.version>
<jctools.version>3.2.0</jctools.version>
<hutool.version>5.5.9</hutool.version>
<oshi.version>5.7.0</oshi.version>
<snakeyaml.version>1.28</snakeyaml.version>
<!-- json和xml解析包 -->
<jackson.version>2.12.1</jackson.version>
<fastjson.version>1.2.51</fastjson.version>
<!-- office文档解析包 -->
<poi.version>4.1.2</poi.version>
<!-- 字节码增强 -->
<javassist.version>3.27.0-GA</javassist.version>
<bytebuddy.version>1.10.22</bytebuddy.version>
<!-- 网络通讯框架 -->
<netty.version>4.1.63.Final</netty.version>
<!-- 分布式zookeeper核心依赖包 -->
<zookeeper.version>3.6.1</zookeeper.version>
<curator.version>5.1.0</curator.version>
<!-- 数据库和缓存 -->
<mongodb-driver-sync.version>4.2.1</mongodb-driver-sync.version>
<jedis.version>3.3.0</jedis.version>
<!-- 消息队列中间件 -->
<rocketmq.version>4.5.2</rocketmq.version>
<!-- elastic search 中间件 -->
<elastic.search.version>7.9.3</elastic.search.version>
<elastic.search.spring.version>4.1.5</elastic.search.spring.version>
<lucene.version>8.6.2</lucene.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.13.1</junit.version>
<!-- java版本和文件编码 -->
<java.version>11</java.version>
<file.encoding>UTF-8</file.encoding>
<jakarta.version>1.3.5</jakarta.version>
<!-- maven核心插件 -->
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<versions-maven-plugin.version>2.8.1</versions-maven-plugin.version>
<project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
<maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
</properties>
<dependencies>
<!-- 依赖的util类库 -->
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>util</artifactId>
<version>${zfoo.util.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
<!-- 动态生成二进制字节码的javassist类库 -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
<!-- 依赖的Spring模块类库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- logback核心包 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- logback的sl4j的实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 依赖的测试库 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<!-- 清理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${file.encoding}</encoding>
</configuration>
</plugin>
<!-- resource资源管理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>${file.encoding}</encoding>
<outputDirectory>${project.build.directory}/resource</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkMode>once</forkMode>
<threadCount>10</threadCount>
<argLine>-Dfile.encoding=${file.encoding}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.scheduler.manager.ISchedulerManager;
import com.zfoo.scheduler.manager.SchedulerManager;
import com.zfoo.scheduler.schema.SchedulerRegisterProcessor;
import com.zfoo.util.ThreadUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import java.lang.reflect.Field;
import java.util.concurrent.ScheduledExecutorService;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SchedulerContext implements ApplicationListener<ApplicationContextEvent>, Ordered {
private static final Logger logger = LoggerFactory.getLogger(SchedulerContext.class);
private static SchedulerContext instance;
private static boolean stop = false;
private ApplicationContext applicationContext;
public static SchedulerContext getSchedulerContext() {
return instance;
}
public static ApplicationContext getApplicationContext() {
return instance.applicationContext;
}
public static ISchedulerManager getSchedulerManager() {
return SchedulerManager.getInstance();
}
public static boolean isStop() {
return stop;
}
public synchronized static void shutdown() {
if (stop) {
return;
}
stop = true;
ISchedulerManager schedulerManager = getSchedulerManager();
if (schedulerManager == null) {
return;
}
try {
Field field = SchedulerManager.class.getDeclaredField("executor");
ReflectionUtils.makeAccessible(field);
var executor = (ScheduledExecutorService) ReflectionUtils.getField(field, schedulerManager);
ThreadUtils.shutdown(executor);
} catch (Throwable e) {
logger.error("Scheduler thread pool failed shutdown.", e);
return;
}
logger.info("Scheduler shutdown gracefully.");
}
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
if (instance != null) {
return;
}
// 初始化上下文
SchedulerContext.instance = this;
instance.applicationContext = event.getApplicationContext();
var beanNames = applicationContext.getBeanDefinitionNames();
var processor = applicationContext.getBean(SchedulerRegisterProcessor.class);
for (var beanName : beanNames) {
processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName);
}
} else if (event instanceof ContextClosedEvent) {
shutdown();
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.manager;
import java.util.concurrent.TimeUnit;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface ISchedulerManager {
/**
* 不断执行的周期循环任务
*/
void scheduleAtFixedRate(Runnable runnable, long period, TimeUnit unit);
/**
* 固定延迟执行的任务
*/
void schedule(Runnable runnable, long delay, TimeUnit unit);
/**
* cron表达式执行的任务
*/
void scheduleCron(Runnable runnable, String cron);
}
@@ -0,0 +1,240 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.manager;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.SchedulerContext;
import com.zfoo.scheduler.model.anno.Scheduler;
import com.zfoo.scheduler.model.vo.SchedulerDefinition;
import com.zfoo.scheduler.util.TimeUtils;
import io.netty.util.concurrent.FastThreadLocalThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Modifier;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SchedulerManager implements ISchedulerManager {
private static final Logger logger = LoggerFactory.getLogger(SchedulerManager.class);
private static final SchedulerManager INSTANCE = new SchedulerManager();
private static final List<SchedulerDefinition> schedulerDefList = new CopyOnWriteArrayList<>();
/**
* 上一次trigger触发时间
*/
private static long lastTriggerTimestamp = 0;
/**
* 在scheduler中,最小的triggerTimestamp
*/
private static long minSchedulerTriggerTimestamp = 0;
private static final long TRIGGER_MILLIS_INTERVAL = TimeUtils.MILLIS_PER_SECOND;
private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new SchedulerThreadFactory());
private static class SchedulerThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
// scheduler-p1-t1 = scheduler-pool-1-thread-1
SchedulerThreadFactory() {
var s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "scheduler-p" + poolNumber.getAndIncrement() + "-t";
}
@Override
public Thread newThread(Runnable runnable) {
var t = new FastThreadLocalThread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
t.setUncaughtExceptionHandler((thread, e) -> {
logger.error(thread.toString(), e);
});
return t;
}
}
static {
executor.scheduleAtFixedRate(() -> {
try {
triggerPerSecond();
} catch (Exception e) {
logger.error("scheduler triggers an error.", e);
}
}, TimeUtils.MILLIS_PER_SECOND, TRIGGER_MILLIS_INTERVAL, TimeUnit.MILLISECONDS);
}
public static SchedulerManager getInstance() {
return INSTANCE;
}
private SchedulerManager() {
}
private static long minSchedulerTriggerTimestamp() {
var minSchedulerOptional = schedulerDefList.stream().min(Comparator.comparingLong(schedulerDef -> schedulerDef.getTriggerTimestamp()));
if (minSchedulerOptional.isPresent()) {
return minSchedulerOptional.get().getTriggerTimestamp();
} else {
logger.error("schedulerDefList:[{}] has no minSchedulerTriggerTimestamp to return. ", JsonUtils.object2String(schedulerDefList));
return 0;
}
}
/**
* 每一秒执行一次,如果这个任务执行时间过长超过,比如10秒,执行完成后,不会再执行10次
*/
private static void triggerPerSecond() {
var timestamp = TimeUtils.currentTimeMillis();
if (CollectionUtils.isEmpty(schedulerDefList)) {
return;
}
/*
有人向前调整过机器时间,重新计算scheduler里的triggerTimestamp
*/
// var diff = timestamp - lastTriggerTimestamp;
if (timestamp < lastTriggerTimestamp) {
for (SchedulerDefinition schedulerDef : schedulerDefList) {
var nextTriggerTimestamp = TimeUtils.getNextTimestampByCronExpression(schedulerDef.getCronExpression(), timestamp);
schedulerDef.setTriggerTimestamp(nextTriggerTimestamp);
}
minSchedulerTriggerTimestamp = minSchedulerTriggerTimestamp();
}
// diff > 0, 没有人调整时间或者有人向后调整过机器时间,可以忽略,因为向后调整时间时间戳一定会大于triggerTimestamp,所以一定会触发
lastTriggerTimestamp = timestamp;
// 如果minSchedulerTriggerTimestamp大于timestamp,说明没有可执行的scheduler
if (timestamp < minSchedulerTriggerTimestamp) {
return;
}
for (var schedulerDef : schedulerDefList) {
if (timestamp >= schedulerDef.getTriggerTimestamp()) {
// 到达触发时间,则执行runnable方法
schedulerDef.getScheduler().invoke();
// 重新设置下一次的触发时间戳
var nextTriggerTimestamp = TimeUtils.getNextTimestampByCronExpression(schedulerDef.getCronExpression(), timestamp);
schedulerDef.setTriggerTimestamp(nextTriggerTimestamp);
}
}
minSchedulerTriggerTimestamp = minSchedulerTriggerTimestamp();
}
public void registerScheduler(Object bean) {
try {
var methods = ReflectionUtils.getMethodsByAnnoInPOJOClass(bean.getClass(), Scheduler.class);
for (var method : methods) {
var scheduler = method.getAnnotation(Scheduler.class);
var paramClazzs = method.getParameterTypes();
if (paramClazzs.length >= 1) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] can not have any parameters", bean.getClass(), method.getName()));
}
var methodName = method.getName();
if (!Modifier.isPublic(method.getModifiers())) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must use 'public' as modifier!", bean.getClass().getName(), methodName));
}
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] can not use 'static' as modifier!", bean.getClass().getName(), methodName));
}
if (!methodName.startsWith("cron")) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must start with 'cron' as method name!"
, bean.getClass().getName(), methodName));
}
var schedulerDef = SchedulerDefinition.valueOf(scheduler.cron(), bean, method);
schedulerDefList.add(schedulerDef);
minSchedulerTriggerTimestamp = minSchedulerTriggerTimestamp();
}
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
@Override
public void scheduleAtFixedRate(Runnable runnable, long period, TimeUnit unit) {
if (SchedulerContext.isStop()) {
return;
}
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
runnable.run();
} catch (Exception e) {
logger.error("scheduleAtFixedRate未知exception异常", e);
} catch (Throwable t) {
logger.error("scheduleAtFixedRate未知error异常", t);
}
}
}, 0, period, unit);
}
@Override
public void schedule(Runnable runnable, long delay, TimeUnit unit) {
if (SchedulerContext.isStop()) {
return;
}
executor.schedule(new Runnable() {
@Override
public void run() {
try {
runnable.run();
} catch (Exception e) {
logger.error("schedule未知exception异常", e);
} catch (Throwable t) {
logger.error("schedule未知error异常", t);
}
}
}, delay, unit);
}
@Override
public void scheduleCron(Runnable runnable, String cron) {
if (SchedulerContext.isStop()) {
return;
}
schedulerDefList.add(SchedulerDefinition.valueOf(cron, runnable));
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.model.anno;
import java.lang.annotation.*;
/**
* @author jaysunxiao
* @version 3.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Scheduler {
String cron();
}
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.model.vo;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.schema.NamespaceHandler;
import com.zfoo.util.security.IdUtils;
import javassist.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class EnhanceUtils {
static {
// 适配Tomcat,因为Tomcat不是用的默认的类加载器,而Javaassist用的是默认的加载器
var classArray = new Class<?>[]{
IScheduler.class
};
var classPool = ClassPool.getDefault();
for (var clazz : classArray) {
if (classPool.find(clazz.getCanonicalName()) == null) {
ClassClassPath classPath = new ClassClassPath(clazz);
classPool.insertClassPath(classPath);
}
}
}
public static IScheduler createScheduler(ReflectScheduler reflectScheduler) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
var classPool = ClassPool.getDefault();
Object bean = reflectScheduler.getBean();
Method method = reflectScheduler.getMethod();
// 定义类名称
CtClass enhanceClazz = classPool.makeClass(EnhanceUtils.class.getCanonicalName() + StringUtils.capitalize(NamespaceHandler.SCHEDULER) + IdUtils.getLocalIntId());
enhanceClazz.addInterface(classPool.get(IScheduler.class.getCanonicalName()));
// 定义类中的一个成员
CtField field = new CtField(classPool.get(bean.getClass().getCanonicalName()), "bean", enhanceClazz);
field.setModifiers(Modifier.PRIVATE);
enhanceClazz.addField(field);
// 定义类的构造器
CtConstructor constructor = new CtConstructor(classPool.get(new String[]{bean.getClass().getCanonicalName()}), enhanceClazz);
constructor.setBody("{this.bean=$1;}");
constructor.setModifiers(Modifier.PUBLIC);
enhanceClazz.addConstructor(constructor);
// 定义类实现的接口方法
CtMethod invokeMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "invoke", null, enhanceClazz);
invokeMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL);
String invokeMethodBody = "{this.bean." + method.getName() + "();}";
invokeMethod.setBody(invokeMethodBody);
enhanceClazz.addMethod(invokeMethod);
// 释放缓存
enhanceClazz.detach();
Class<?> resultClazz = enhanceClazz.toClass(IScheduler.class);
Constructor<?> resultConstructor = resultClazz.getConstructor(bean.getClass());
return (IScheduler) resultConstructor.newInstance(bean);
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.model.vo;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IScheduler {
void invoke();
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.model.vo;
import com.zfoo.protocol.util.ReflectionUtils;
import java.lang.reflect.Method;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ReflectScheduler implements IScheduler {
private Object bean;
private Method method;
public static ReflectScheduler valueOf(Object bean, Method method) {
var scheduler = new ReflectScheduler();
scheduler.bean = bean;
scheduler.method = method;
return scheduler;
}
@Override
public void invoke() {
ReflectionUtils.invokeMethod(bean, method);
}
public Object getBean() {
return bean;
}
public Method getMethod() {
return method;
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.model.vo;
/**
* @author jaysunxiao
* @version 3.0
*/
public class RunnableScheduler implements IScheduler {
private Runnable runnable;
public static RunnableScheduler valueOf(Runnable runnable) {
var scheduler = new RunnableScheduler();
scheduler.runnable = runnable;
return scheduler;
}
@Override
public void invoke() {
runnable.run();
}
}
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.model.vo;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.scheduler.util.TimeUtils;
import javassist.CannotCompileException;
import javassist.NotFoundException;
import org.springframework.scheduling.support.CronExpression;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SchedulerDefinition {
private CronExpression cronExpression;
private IScheduler scheduler;
/**
* 触发时间戳,只要当前时间戳大于这个触发事件戳都视为可以触发
*/
private long triggerTimestamp;
public static SchedulerDefinition valueOf(String cron, Object bean, Method method) throws NoSuchMethodException, IllegalAccessException, InstantiationException, CannotCompileException, NotFoundException, InvocationTargetException {
var schedulerDef = new SchedulerDefinition();
var cronExpression = CronExpression.parse(cron);
schedulerDef.cronExpression = cronExpression;
// 字节码增强,避免反射
schedulerDef.scheduler = EnhanceUtils.createScheduler(ReflectScheduler.valueOf(bean, method));
schedulerDef.triggerTimestamp = TimeUtils.getNextTimestampByCronExpression(cronExpression, TimeUtils.currentTimeMillis());
ReflectionUtils.makeAccessible(method);
return schedulerDef;
}
public static SchedulerDefinition valueOf(String cron, Runnable runnable) {
var schedulerDef = new SchedulerDefinition();
var cronExpression = CronExpression.parse(cron);
schedulerDef.cronExpression = cronExpression;
schedulerDef.scheduler = RunnableScheduler.valueOf(runnable);
schedulerDef.triggerTimestamp = TimeUtils.getNextTimestampByCronExpression(cronExpression, TimeUtils.currentTimeMillis());
return schedulerDef;
}
public CronExpression getCronExpression() {
return cronExpression;
}
public void setCronExpression(CronExpression cronExpression) {
this.cronExpression = cronExpression;
}
public IScheduler getScheduler() {
return scheduler;
}
public void setScheduler(IScheduler scheduler) {
this.scheduler = scheduler;
}
public long getTriggerTimestamp() {
return triggerTimestamp;
}
public void setTriggerTimestamp(long triggerTimestamp) {
this.triggerTimestamp = triggerTimestamp;
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.schema;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NamespaceHandler extends NamespaceHandlerSupport {
public static final String SCHEDULER = "scheduler";
@Override
public void init() {
registerBeanDefinitionParser(SCHEDULER, new SchedulerDefinitionParser());
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.schema;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.SchedulerContext;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SchedulerDefinitionParser implements BeanDefinitionParser {
private final String SCHEDULER_ID = "id";
@Override
public AbstractBeanDefinition parse(Element element, ParserContext parserContext) {
Class<?> clazz;
String name;
BeanDefinitionBuilder builder;
// 注册SchedulerSpringContext
clazz = SchedulerContext.class;
name = StringUtils.uncapitalize(clazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition());
// 注册SchedulerRegisterProcessor
clazz = SchedulerRegisterProcessor.class;
name = StringUtils.uncapitalize(clazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition());
// 注册SchedulerManager
String schedulerId = element.getAttribute(SCHEDULER_ID);
return builder.getBeanDefinition();
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.schema;
import com.zfoo.scheduler.SchedulerContext;
import com.zfoo.scheduler.manager.SchedulerManager;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SchedulerRegisterProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (SchedulerContext.getSchedulerContext() == null) {
return bean;
}
SchedulerManager schedulerManager = (SchedulerManager) SchedulerContext.getSchedulerManager();
schedulerManager.registerScheduler(bean);
return bean;
}
}
@@ -0,0 +1,373 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.util;
import com.zfoo.scheduler.SchedulerContext;
import com.zfoo.scheduler.manager.SchedulerManager;
import io.netty.util.concurrent.FastThreadLocal;
import org.springframework.scheduling.support.CronExpression;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class TimeUtils {
private static long timestamp = System.currentTimeMillis();
// 一秒钟对应的纳秒数
public static final long NANO_PER_SECOND = 1_000_000_000;
// 一秒钟对应的毫秒数
public static final long MILLIS_PER_SECOND = 1 * 1000;
// 一分钟对应的毫秒数
public static final long MILLIS_PER_MINUTE = 1 * 60 * MILLIS_PER_SECOND;
// 一个小时对应的毫秒数
public static final long MILLIS_PER_HOUR = 1 * 60 * MILLIS_PER_MINUTE;
// 一天对应的毫秒数
public static final long MILLIS_PER_DAY = 1 * 24 * MILLIS_PER_HOUR;
// 一周对应的毫秒数
public static final long MILLIS_PER_WEEK = 1 * 7 * MILLIS_PER_HOUR;
// 默认的时区
public static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();
// 默认的时区Id
public static final ZoneId DEFAULT_ZONE_ID = TimeZone.getDefault().toZoneId();
// 统一的时间格式模板
public static final String DATE_FORMAT_TEMPLATE = "yyyy-MM-dd HH:mm:ss";
// 简单的时间格式模板
public static final String SIMPLE_DATE_FORMAT_TEMPLATE = "yyyyMMddHHmmss";
// 统一的时间格式模板
public static final String DATE_FORMAT_TEMPLATE_FOR_DAY = "yyyy-MM-dd";
private static final FastThreadLocal<SimpleDateFormat> DATE_FORMAT = new FastThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(DATE_FORMAT_TEMPLATE);
}
};
private static final FastThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT = new FastThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(SIMPLE_DATE_FORMAT_TEMPLATE);
}
};
private static final FastThreadLocal<SimpleDateFormat> DATE_FORMAT_FOR_DAY = new FastThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(DATE_FORMAT_TEMPLATE_FOR_DAY);
}
};
static {
SchedulerContext.getSchedulerManager();
SchedulerManager.getInstance();
}
/**
* 获取精确的时间戳
*/
public static long currentTimeMillis() {
timestamp = System.currentTimeMillis();
return timestamp;
}
/**
* 获取最多只有一秒延迟的粗略时间戳,适用于对时间精度要求不高的场景,最多只有一秒误差
* <p>
* 比System.currentTimeMillis()的性能高10倍
*/
public static long now() {
return timestamp;
}
// --------------------------------------日期格式--------------------------------------
/**
* 把日期字符串转换成Date对象,统一的日期模板要遵循DATE_FORMAT_TEMPLATE="yyyy-MM-dd HH:mm:ss"
*
* @param dateString 日期字符串,如:2018-02-12 10:12:50
* @return <code>Date</code>
* @throws ParseException 解析异常
*/
public static Date stringToDate(String dateString) throws ParseException {
return DATE_FORMAT.get().parse(dateString);
}
/**
* 把long类型的时间戳转换成字符串格式的时间
*
* @param time 时间戳
* @return yyyy-MM-dd HH:mm:ss
*/
public static String timeToString(long time) {
return dateToString(new Date(time));
}
public static String dateToString(Date date) {
return DATE_FORMAT.get().format(date);
}
public static String simpleDateString() {
return SIMPLE_DATE_FORMAT.get().format(new Date(now()));
}
public static Date dayStringToDate(String dateString) throws ParseException {
return DATE_FORMAT_FOR_DAY.get().parse(dateString);
}
// --------------------------------------日期判断--------------------------------------
/**
* <p>Checks if two date objects are on the same day ignoring time.</p>
*
* <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
* 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
* </p>
*
* @param date1 the first date, not altered, not null
* @param date2 the second date, not altered, not null
* @return true if they represent the same day
* @throws IllegalArgumentException if either date is <code>null</code>
*/
public static boolean isSameDay(Date date1, Date date2) {
if (date1 == null || date2 == null) {
throw new IllegalArgumentException("The date must not be null");
}
var cal1 = Calendar.getInstance();
cal1.setTime(date1);
var cal2 = Calendar.getInstance();
cal2.setTime(date2);
return isSameDay(cal1, cal2);
}
public static boolean isSameDay(long time1, long time2) {
var cal1 = Calendar.getInstance();
cal1.setTimeInMillis(time1);
var cal2 = Calendar.getInstance();
cal2.setTimeInMillis(time2);
return isSameDay(cal1, cal2);
}
/**
* <p>Checks if two calendar objects are on the same day ignoring time.</p>
*
* <p>28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true.
* 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false.
* </p>
*
* @param cal1 the first calendar, not altered, not null
* @param cal2 the second calendar, not altered, not null
* @return true if they represent the same day
* @throws IllegalArgumentException if either calendar is <code>null</code>
*/
public static boolean isSameDay(Calendar cal1, Calendar cal2) {
if (cal1 == null || cal2 == null) {
throw new IllegalArgumentException("The date must not be null");
}
return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
}
public static String dateFormatForDayString(long time) {
return DATE_FORMAT_FOR_DAY.get().format(new Date(time));
}
/**
* 判断两个日期是否是同一周,设置周一为一周的第一天
* <p>
* 2004-12-25”是星期六,也就是说它是2004年中第52周的星期六,那么“2004-12-26”到底是2004年的第几周哪,java中经测试取得的它的Week值是1,
* 那么也就是说它被看作2005年的第一周了,这个处理是比较好的。可以用来判断“2004-12-26”和“2005-1-1”是同一周。
*/
public static boolean isSameWeek(long time1, long time2) {
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTimeInMillis(time1);
cal2.setTimeInMillis(time2);
cal1.setFirstDayOfWeek(Calendar.MONDAY);
cal2.setFirstDayOfWeek(Calendar.MONDAY);
int yearDiff = cal1.get(Calendar.YEAR) - cal2.get(Calendar.YEAR);
if (yearDiff == 0 && cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) {
// yearDiff==0,说明是同一年
if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) {
return true;
}
} else if (yearDiff == 1 && cal2.get(Calendar.MONTH) == 11) {
//yearDiff==1,说明cal比cal2大一年;java的一月用"0"标识,那么12月用"11"
if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) {
return true;
}
} else if (yearDiff == -1 && cal1.get(Calendar.MONTH) == 11) {
//yearDiff==-1,说明cal比cal2小一年
if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) {
return true;
}
}
return false;
}
public static boolean isSameMonth(long time1, long time2) {
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTimeInMillis(time1);
cal2.setTimeInMillis(time2);
return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
&& cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH);
}
// --------------------------------------获取相关时间戳--------------------------------------
/**
* 获取给定时间戳对应的日期的0点时间戳
*/
public static long getZeroTimeOfDay(long time) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTimeInMillis();
}
/**
* 获取给定时间戳对应的日期的最后时刻时间戳
*/
public static long getLastTimeOfDay(long time) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time);
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
return cal.getTimeInMillis();
}
// 获取给定时间戳对应的日期的昨天这个时刻的时间戳
public static long getYesterdayTimeOfDay(long time) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time);
cal.add(Calendar.DATE, -1);
return cal.getTimeInMillis();
}
// 获取给定时间戳对应小时的起始时间戳
public static long getStartTimeOfHour(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var monday = localDateTime.withMinute(0).withSecond(0).withNano(0);
return TimeUtils.localDateTimeToTimestamp(monday);
}
// 获取给定时间戳对应周的第一天的起始时间戳
public static long getStartTimeOfWeek(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var monday = localDateTime.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY)).plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
return TimeUtils.localDateTimeToTimestamp(monday);
}
// 获取给定时间戳对应月的第一天的起始时间戳
public static long getStartTimeOfMonth(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var oneOfMonth = localDateTime.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0);
return TimeUtils.localDateTimeToTimestamp(oneOfMonth);
}
// 获取给定时间戳对应月的最后一天的结束时间戳
public static long getEndTimeOfMonth(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var oneOfMonth = localDateTime.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0);
return TimeUtils.localDateTimeToTimestamp(oneOfMonth);
}
//获取上一个月月初的时间
public static long getLastMonthStart(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var lastMonth = localDateTime.minusMonths(1);
LocalDateTime with = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0);
return TimeUtils.localDateTimeToTimestamp(with);
}
//获取上一个月月末的时间
public static long getLastMonthEnd(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var lastMonth = localDateTime.minusMonths(1);
LocalDateTime with = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0);
return TimeUtils.localDateTimeToTimestamp(with);
}
//获取下一个月月初的时间
public static long getNextMonthStart(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var lastMonth = localDateTime.plusMonths(1);
LocalDateTime with = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0);
return TimeUtils.localDateTimeToTimestamp(with);
}
// 获取下一个月月末的时间
public static long getNextMonthEnd(long time) {
var localDateTime = TimeUtils.timestampToLocalDateTime(time);
var lastMonth = localDateTime.plusMonths(1);
LocalDateTime with = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0);
return TimeUtils.localDateTimeToTimestamp(with);
}
/**
* LocalDateTime转毫秒时间戳
*
* @param localDateTime LocalDateTime
* @return 时间戳
*/
public static Long localDateTimeToTimestamp(LocalDateTime localDateTime) {
var zoneId = ZoneId.systemDefault();
var instant = localDateTime.atZone(zoneId).toInstant();
return instant.toEpochMilli();
}
/**
* 时间戳转LocalDateTime
*
* @param timestamp 时间戳
* @return LocalDateTime
*/
public static LocalDateTime timestampToLocalDateTime(long timestamp) {
var instant = Instant.ofEpochMilli(timestamp);
var zone = ZoneId.systemDefault();
return LocalDateTime.ofInstant(instant, zone);
}
// --------------------------------------cron表达式--------------------------------------
public static long getNextTimestampByCronExpression(CronExpression expression, long currentTimestamp) {
var next = expression.next(ZonedDateTime.ofInstant(Instant.ofEpochMilli(currentTimestamp), DEFAULT_ZONE_ID));
if (next == null) {
return Long.MAX_VALUE;
}
return next.toInstant().toEpochMilli();
}
}
@@ -0,0 +1 @@
http\://www.zfoo.com/schema/scheduler=com.zfoo.scheduler.schema.NamespaceHandler
@@ -0,0 +1 @@
http\://www.zfoo.com/schema/scheduler-1.0.xsd=scheduler-1.0.xsd
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.zfoo.com/schema/scheduler"
targetNamespace="http://www.zfoo.com/schema/scheduler"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="scheduler">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler;
import com.zfoo.util.ThreadUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* cron(译为克龙)代表100万年,是英文单词中最大的时间单位。
* google(译为古戈尔)代表10的100次方,足够穷尽宇宙万物
*
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class ApplicationTest {
@Test
public void startSchedulerTest() {
// 加载配置文件,配置文件中必须引入scheduler
var context = new ClassPathXmlApplicationContext("application.xml");
ThreadUtils.sleep(Long.MAX_VALUE);
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler;
import com.zfoo.scheduler.model.anno.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author jaysunxiao
* @version 3.0
*/
@Component
public class SchedulerController {
private static final Logger logger = LoggerFactory.getLogger(SchedulerController.class);
@Scheduler(cron = "0/5 * * * * ?")
public void cronScheduler1() {
logger.info("scheduler1 每5秒时间调度任务");
}
@Scheduler(cron = "0,10,20,40 * * * * ?")
public void cronScheduler2() {
logger.info("scheduler2 每分钟的10秒,20秒,40秒调度任务");
}
}
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.scheduler.util;
import com.zfoo.util.ThreadUtils;
import org.junit.Ignore;
import org.junit.Test;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
/**
* @author jaysunxiao
* @version 3.0
*/
public class TimeUtilsTest {
@Test
public void testLocalDate() {
// 取当前日期:
LocalDate today = LocalDate.now(); // -> 2014-12-24
// 根据年月日取日期:
LocalDate crischristmas = LocalDate.of(2014, 12, 25); // -> 2014-12-25
// 根据字符串取:
LocalDate endOfFeb = LocalDate.parse("2014-02-28"); // 严格按照ISO yyyy-MM-dd验证,02写成2都不行,当然也有一个重载方法允许自己定义格式
LocalDate.parse("2014-02-26"); // 无效日期无法通过:DateTimeParseException: Invalid date
// 取本月第1天:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 2017-03-01
// 取本月第2天:
LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2017-03-02
// 取本月最后一天,再也不用计算是28,29,30还是31:
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2017-12-31
// 取下一天:
LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // 变成了2018-01-01
// 取2017年1月第一个周一,用Calendar要死掉很多脑细胞:
LocalDate firstMondayOf2015 = LocalDate.parse("2017-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2017-01-02
}
@Test
public void testLocalTime() {
System.out.println(Long.MAX_VALUE);
}
@Test
public void testLocalDateTime() {
LocalDate date = LocalDate.of(2018, 12, 4);
LocalTime time = LocalTime.of(0, 0, 0);
LocalDateTime localDateTime = LocalDateTime.of(date, time);
System.out.println(localDateTime);
//将localDateTime转换成时间戳
System.out.println("localDateTime:" + java.sql.Timestamp.valueOf(localDateTime).getTime());
}
/**
* 计算周五的活动下次开启的时间
*/
@Test
public void test() {
LocalDate date = LocalDate.of(2018, 12, 6);
LocalTime time = LocalTime.of(0, 0, 0);
int days = DayOfWeek.FRIDAY.getValue() - date.getDayOfWeek().getValue();
LocalDate localDate = (days <= 0) ? date.plusDays(DayOfWeek.SUNDAY.getValue() + days) : date.plusDays(days);
System.out.println(localDate);
LocalDateTime localDateTime = LocalDateTime.of(date, time);
System.out.println(localDateTime);
System.out.println("date:" + new Date(java.sql.Timestamp.valueOf(localDateTime).getTime()));
}
@Ignore
@Test
public void nowTest() {
for (int i = 0; i < 100; i++) {
System.out.println(TimeUtils.now());
ThreadUtils.sleep(1000);
}
}
}
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:scheduler="http://www.zfoo.com/schema/scheduler"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.zfoo.com/schema/scheduler
http://www.zfoo.com/schema/scheduler-1.0.xsd">
<context:component-scan base-package="com.zfoo.scheduler"/>
<scheduler:scheduler id="schedulerManager"/>
</beans>
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<contextName>com.zfoo.scheduler</contextName>
<property name="LOG_HOME" value="log/scheduler"/>
<property name="PATTERN_FILE"
value="%d{yyyy-MM-dd HH:mm:ss} [%5level] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<property name="PATTERN_CONSOLE"
value="%d{yyyy-MM-dd HH:mm:ss} [%highlight(%5level)] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<!-- 负责写日志,控制台日志,会打印所有的包的所有级别日志 -->
<appender name="zfoo_console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN_CONSOLE}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 根logger -->
<root level="info">
<appender-ref ref="zfoo_console"/>
</root>
<!--
- 1.name:包名或类名,用来指定受此logger约束的某一个包或者具体的某一个类
- 2.未设置打印级别,所以继承他的上级<root>的日志级别“DEBUG”
- 3.未设置additivity,默认为true,将此logger的打印信息向上级传递;
- 4.未设置appender,此logger本身不打印任何信息,级别为“DEBUG”及大于“DEBUG”的日志信息传递给root,
- root接到下级传递的信息,交给已经配置好的名为“STDOUT”的appender处理,“STDOUT”appender将信息打印到控制台;
-->
<logger name="ch.qos.logback" level="info"/>
<!--*******************************************Spring********************************************************-->
<!--logger中的name是指代码的包名或类名,路径要写全,可以配置不同包中的日志输出到不同的文件中。level是日志输出级别 -->
<!--过滤掉spring的一些无用的DEBUG信息-->
<logger name="org.springframework" level="info"/>
<!-- additivity="false"表示不继承父logger的配置和父类没有关系-->
<logger name="org.springframework.core" level="warn"/>
<!--*******************************************Netty*********************************************************-->
<logger name="io.netty" level="info"/>
</configuration>