Spring boot定时任务的原理及动态创建详解

v一、前言

定时任务一般是项目中都需要用到的,可以用于定时处理一些特殊的任务。这篇文章主要给大家介绍了关于Spring boot定时任务的原理及动态创建的相关内容,下面来一起看看详细的介绍吧

上周工作遇到了一个需求,同步多个省份销号数据,解绑微信粉丝。分省定时将销号数据放到SFTP服务器上,我需要开发定时任务去解析文件。因为是多省份,服务器、文件名规则、数据规则都不一定,所以要做成可配置是有一定难度的。数据规则这块必须强烈要求统一,服务器、文件名规则都可以从配置中心去读。每新增一个省份的配置,后台感知到后,动态生成定时任务。

v二、Springboot引入定时任务核心配置

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {

}

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

 @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
 @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
 return new ScheduledAnnotationBeanPostProcessor();
 }

}

接下来主要看一下这个核心后置处理器:ScheduledAnnotationBeanPostProcessor 。

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
 if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
  bean instanceof ScheduledExecutorService) {
 // Ignore AOP infrastructure such as scoped proxies.
 return bean;
 }

 Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
 if (!this.nonAnnotatedClasses.contains(targetClass)) {
 Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
  (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
   Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
    method, Scheduled.class, Schedules.class);
   return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
  });
 if (annotatedMethods.isEmpty()) {
  this.nonAnnotatedClasses.add(targetClass);
  if (logger.isTraceEnabled()) {
  logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
  }
 }
 else {
  // Non-empty set of methods
  annotatedMethods.forEach((method, scheduledMethods) ->
   scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
  if (logger.isTraceEnabled()) {
  logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
   "': " + annotatedMethods);
  }
 }
 }
 return bean;
}

1、处理Scheduled注解,通过ScheduledTaskRegistrar注册定时任务。

private void finishRegistration() {
 if (this.scheduler != null) {
 this.registrar.setScheduler(this.scheduler);
 }

 if (this.beanFactory instanceof ListableBeanFactory) {
 Map<String, SchedulingConfigurer> beans =
  ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);
 List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());
 AnnotationAwareOrderComparator.sort(configurers);
 for (SchedulingConfigurer configurer : configurers) {
  configurer.configureTasks(this.registrar);
 }
 }

 if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {
 Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");
 try {
  // Search for TaskScheduler bean...
  this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
 }
 catch (NoUniqueBeanDefinitionException ex) {
  logger.trace("Could not find unique TaskScheduler bean", ex);
  try {
  this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, true));
  }
  catch (NoSuchBeanDefinitionException ex2) {
  if (logger.isInfoEnabled()) {
   logger.info("More than one TaskScheduler bean exists within the context, and " +
    "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
    "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
    "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
    ex.getBeanNamesFound());
  }
  }
 }
 catch (NoSuchBeanDefinitionException ex) {
  logger.trace("Could not find default TaskScheduler bean", ex);
  // Search for ScheduledExecutorService bean next...
  try {
  this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, false));
  }
  catch (NoUniqueBeanDefinitionException ex2) {
  logger.trace("Could not find unique ScheduledExecutorService bean", ex2);
  try {
   this.registrar.setScheduler(resolveSchedulerBean(this.beanFactory, ScheduledExecutorService.class, true));
  }
  catch (NoSuchBeanDefinitionException ex3) {
   if (logger.isInfoEnabled()) {
   logger.info("More than one ScheduledExecutorService bean exists within the context, and " +
    "none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +
    "(possibly as an alias); or implement the SchedulingConfigurer interface and call " +
    "ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +
    ex2.getBeanNamesFound());
   }
  }
  }
  catch (NoSuchBeanDefinitionException ex2) {
  logger.trace("Could not find default ScheduledExecutorService bean", ex2);
  // Giving up -> falling back to default scheduler within the registrar...
  logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");
  }
 }
 }

 this.registrar.afterPropertiesSet();
}

  1、通过一系列的SchedulingConfigurer动态配置ScheduledTaskRegistrar。

  2、向ScheduledTaskRegistrar注册一个TaskScheduler(用于对Runnable的任务进行调度,它包含有多种触发规则)。

  3、registrar.afterPropertiesSet(),在这开始安排所有的定时任务开始执行了。

protected void scheduleTasks() {
 if (this.taskScheduler == null) {
 this.localExecutor = Executors.newSingleThreadScheduledExecutor();
 this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
 }
 if (this.triggerTasks != null) {
 for (TriggerTask task : this.triggerTasks) {
  addScheduledTask(scheduleTriggerTask(task));
 }
 }
 if (this.cronTasks != null) {
 for (CronTask task : this.cronTasks) {
  addScheduledTask(scheduleCronTask(task));
 }
 }
 if (this.fixedRateTasks != null) {
 for (IntervalTask task : this.fixedRateTasks) {
  addScheduledTask(scheduleFixedRateTask(task));
 }
 }
 if (this.fixedDelayTasks != null) {
 for (IntervalTask task : this.fixedDelayTasks) {
  addScheduledTask(scheduleFixedDelayTask(task));
 }
 }
}

  1、TriggerTask:动态定时任务。通过Trigger#nextExecutionTime 给定的触发上下文确定下一个执行时间。

  2、CronTask:动态定时任务,TriggerTask子类。通过cron表达式确定的时间触发下一个任务执行。

  3、IntervalTask:一定时间延迟之后,周期性执行的任务。

  4、taskScheduler 如果为空,默认是ConcurrentTaskScheduler,并使用默认单线程的ScheduledExecutor。

v三、主要看一下CronTask工作原理

ScheduledTaskRegistrar.java
@Nullable
public ScheduledTask scheduleCronTask(CronTask task) {
 ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
 boolean newTask = false;
 if (scheduledTask == null) {
 scheduledTask = new ScheduledTask(task);
 newTask = true;
 }
 if (this.taskScheduler != null) {
 scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
 }
 else {
 addCronTask(task);
 this.unresolvedTasks.put(task, scheduledTask);
 }
 return (newTask ? scheduledTask : null);
}

ConcurrentTaskScheduler.java
@Override
@Nullable
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
 try {
 if (this.enterpriseConcurrentScheduler) {
  return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);
 }
 else {
  ErrorHandler errorHandler =
   (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
  return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();
 }
 }
 catch (RejectedExecutionException ex) {
 throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);
 }
}

ReschedulingRunnable.java
@Nullable
public ScheduledFuture<?> schedule() {
 synchronized (this.triggerContextMonitor) {
 this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);
 if (this.scheduledExecutionTime == null) {
  return null;
 }
 long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();
 this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
 return this;
 }
}

private ScheduledFuture<?> obtainCurrentFuture() {
 Assert.state(this.currentFuture != null, "No scheduled future");
 return this.currentFuture;
}

@Override
public void run() {
 Date actualExecutionTime = new Date();
 super.run();
 Date completionTime = new Date();
 synchronized (this.triggerContextMonitor) {
 Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
 this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
 if (!obtainCurrentFuture().isCancelled()) {
  schedule();
 }
 }
}

  1、最终将task和trigger都封装到了ReschedulingRunnable中。

  2、ReschedulingRunnable实现了任务重复调度(schedule方法中调用调度器executor并传入自身对象,executor会调用run方法,run方法又调用了schedule方法)。

  3、ReschedulingRunnable schedule方法加了同步锁,只能有一个线程拿到下次执行时间并加入执行器的调度。

  4、不同的ReschedulingRunnable对象之间在线程池够用的情况下是不会相互影响的,也就是说满足线程池的条件下,TaskScheduler的schedule方法的多次调用是可以交叉执行的。

ScheduledThreadPoolExecutor.java
public ScheduledFuture<?> schedule(Runnable command,
     long delay,
     TimeUnit unit) {
 if (command == null || unit == null)
 throw new NullPointerException();
 RunnableScheduledFuture<?> t = decorateTask(command,
 new ScheduledFutureTask<Void>(command, null,
     triggerTime(delay, unit)));
 delayedExecute(t);
 return t;
}

private void delayedExecute(RunnableScheduledFuture<?> task) {
 if (isShutdown())
 reject(task);
 else {
 super.getQueue().add(task);
 if (isShutdown() &&
  !canRunInCurrentRunState(task.isPeriodic()) &&
  remove(task))
  task.cancel(false);
 else
  ensurePrestart();
 }
}

  ScheduledFutureTask 工作原理如下图所示【太懒了,不想画图了,盗图一张】。

 

  1、ScheduledFutureTask会放入优先阻塞队列:ScheduledThreadPoolExecutor.DelayedWorkQueue(二叉最小堆实现)

  2、上图中的Thread对象即ThreadPoolExecutor.Worker,实现了Runnable接口

/**
 * Creates with given first task and thread from ThreadFactory.
 * @param firstTask the first task (null if none)
 */
Worker(Runnable firstTask) {
 setState(-1); // inhibit interrupts until runWorker
 this.firstTask = firstTask;
 this.thread = getThreadFactory().newThread(this);
}

/** Delegates main run loop to outer runWorker */
public void run() {
 runWorker(this);
}

  1、Worker中维护了Thread对象,Thread对象的Runnable实例即Worker自身

  2、ThreadPoolExecutor#addWorker方法中会创建Worker对象,然后拿到Worker中的thread实例并start,这样就创建了线程池中的一个线程实例

  3、Worker的run方法会调用ThreadPoolExecutor#runWorker方法,这才是任务最终被执行的地方,该方法示意如下

  (1)首先取传入的task执行,如果task是null,只要该线程池处于运行状态,就会通过getTask方法从workQueue中取任务。ThreadPoolExecutor的execute方法会在无法产生core线程的时候向  workQueue队列中offer任务。
getTask方法从队列中取task的时候会根据相关配置决定是否阻塞和阻塞多久。如果getTask方法结束,返回的是null,runWorker循环结束,执行processWorkerExit方法。
至此,该线程结束自己的使命,从线程池中“消失”。

  (2)在开始执行任务之前,会调用Worker的lock方法,目的是阻止task正在被执行的时候被interrupt,通过调用clearInterruptsForTaskRun方法来保证的(后面可以看一下这个方法),该线程没有自己的interrupt set了。

  (3)beforeExecute和afterExecute方法用于在执行任务前后执行一些自定义的操作,这两个方法是空的,留给继承类去填充功能。

我们可以在beforeExecute方法中抛出异常,这样task不会被执行,而且在跳出该循环的时候completedAbruptly的值是true,表示the worker died due to user exception,会用decrementWorkerCount调整wc。

  (4)因为Runnable的run方法不能抛出Throwables异常,所以这里重新包装异常然后抛出,抛出的异常会使当当前线程死掉,可以在afterExecute中对异常做一些处理。

  (5)afterExecute方法也可能抛出异常,也可能使当前线程死掉。

v四、动态创建定时任务

v  TaskConfiguration 配置类

@Configuration
@EnableScheduling
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class TaskConfiguration {

 @Bean(name = ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME)
 @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 public ScheduledExecutorService scheduledAnnotationProcessor() {
 return Executors.newScheduledThreadPool(5, new DefaultThreadFactory());
 }

 private static class DefaultThreadFactory 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;

 DefaultThreadFactory() {
  SecurityManager s = System.getSecurityManager();
  group = (s != null) ? s.getThreadGroup() :
   Thread.currentThread().getThreadGroup();
  namePrefix = "pool-" +
   poolNumber.getAndIncrement() +
   "-schedule-";
 }

 @Override
 public Thread newThread(Runnable r) {
  Thread t = new Thread(group, r,
   namePrefix + threadNumber.getAndIncrement(),
   0);
  if (t.isDaemon()) {
  t.setDaemon(false);
  }
  if (t.getPriority() != Thread.NORM_PRIORITY) {
  t.setPriority(Thread.NORM_PRIORITY);
  }
  return t;
 }
 }
}

  1、保证ConcurrentTaskScheduler不使用默认单线程的ScheduledExecutor,而是corePoolSize=5的线程池

  2、自定义线程池工厂类

v  DynamicTask 动态定时任务

@Configuration
public class DynamicTask implements SchedulingConfigurer {
 private static Logger LOGGER = LoggerFactory.getLogger(DynamicTask.class);

 private static final ExecutorService es = new ThreadPoolExecutor(10, 20,
   0L, TimeUnit.MILLISECONDS,
   new LinkedBlockingQueue<>(10),
   new DynamicTaskConsumeThreadFactory());

 private volatile ScheduledTaskRegistrar registrar;
 private final ConcurrentHashMap<String, ScheduledFuture<?>> scheduledFutures = new ConcurrentHashMap<>();
 private final ConcurrentHashMap<String, CronTask> cronTasks = new ConcurrentHashMap<>();

 private volatile List<TaskConstant> taskConstants = Lists.newArrayList();

 @Override
 public void configureTasks(ScheduledTaskRegistrar registrar) {
  this.registrar = registrar;
  this.registrar.addTriggerTask(() -> {
     if (!CollectionUtils.isEmpty(taskConstants)) {
      LOGGER.info("检测动态定时任务列表...");
      List<TimingTask> tts = new ArrayList<>();
      taskConstants
        .forEach(taskConstant -> {
         TimingTask tt = new TimingTask();
         tt.setExpression(taskConstant.getCron());
         tt.setTaskId("dynamic-task-" + taskConstant.getTaskId());
         tts.add(tt);
        });
      this.refreshTasks(tts);
     }
    }
    , triggerContext -> new PeriodicTrigger(5L, TimeUnit.SECONDS).nextExecutionTime(triggerContext));
 }

 public List<TaskConstant> getTaskConstants() {
  return taskConstants;
 }

 private void refreshTasks(List<TimingTask> tasks) {
  //取消已经删除的策略任务
  Set<String> taskIds = scheduledFutures.keySet();
  for (String taskId : taskIds) {
   if (!exists(tasks, taskId)) {
    scheduledFutures.get(taskId).cancel(false);
   }
  }
  for (TimingTask tt : tasks) {
   String expression = tt.getExpression();
   if (StringUtils.isBlank(expression) || !CronSequenceGenerator.isValidExpression(expression)) {
    LOGGER.error("定时任务DynamicTask cron表达式不合法: " + expression);
    continue;
   }
   //如果配置一致,则不需要重新创建定时任务
   if (scheduledFutures.containsKey(tt.getTaskId())
     && cronTasks.get(tt.getTaskId()).getExpression().equals(expression)) {
    continue;
   }
   //如果策略执行时间发生了变化,则取消当前策略的任务
   if (scheduledFutures.containsKey(tt.getTaskId())) {
    scheduledFutures.remove(tt.getTaskId()).cancel(false);
    cronTasks.remove(tt.getTaskId());
   }
   CronTask task = new CronTask(tt, expression);
   ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
   cronTasks.put(tt.getTaskId(), task);
   scheduledFutures.put(tt.getTaskId(), future);
  }
 }

 private boolean exists(List<TimingTask> tasks, String taskId) {
  for (TimingTask task : tasks) {
   if (task.getTaskId().equals(taskId)) {
    return true;
   }
  }
  return false;
 }

 @PreDestroy
 public void destroy() {
  this.registrar.destroy();
 }

 public static class TaskConstant {
  private String cron;
  private String taskId;

  public String getCron() {
   return cron;
  }

  public void setCron(String cron) {
   this.cron = cron;
  }

  public String getTaskId() {
   return taskId;
  }

  public void setTaskId(String taskId) {
   this.taskId = taskId;
  }
 }

 private class TimingTask implements Runnable {
  private String expression;

  private String taskId;

  public String getTaskId() {
   return taskId;
  }

  public void setTaskId(String taskId) {
   this.taskId = taskId;
  }

  @Override
  public void run() {
   //设置队列大小10
   LOGGER.error("当前CronTask: " + this);
   DynamicBlockingQueue queue = new DynamicBlockingQueue(3);
   es.submit(() -> {
    while (!queue.isDone() || !queue.isEmpty()) {
     try {
      String content = queue.poll(500, TimeUnit.MILLISECONDS);
      if (StringUtils.isBlank(content)) {
       return;
      }
      LOGGER.info("DynamicBlockingQueue 消费:" + content);
      TimeUnit.MILLISECONDS.sleep(500);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   });

   //队列放入数据
   for (int i = 0; i < 5; ++i) {
    try {
     queue.put(String.valueOf(i));
     LOGGER.info("DynamicBlockingQueue 生产:" + i);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   queue.setDone(true);
  }

  public String getExpression() {
   return expression;
  }

  public void setExpression(String expression) {
   this.expression = expression;
  }

  @Override
  public String toString() {
   return ReflectionToStringBuilder.toString(this
     , ToStringStyle.JSON_STYLE
     , false
     , false
     , TimingTask.class);
  }

 }

 /**
  * 队列消费线程工厂类
  */
 private static class DynamicTaskConsumeThreadFactory 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;

  DynamicTaskConsumeThreadFactory() {
   SecurityManager s = System.getSecurityManager();
   group = (s != null) ? s.getThreadGroup() :
     Thread.currentThread().getThreadGroup();
   namePrefix = "pool-" +
     poolNumber.getAndIncrement() +
     "-dynamic-task-";
  }

  @Override
  public Thread newThread(Runnable r) {
   Thread t = new Thread(group, r,
     namePrefix + threadNumber.getAndIncrement(),
     0);
   if (t.isDaemon()) {
    t.setDaemon(false);
   }
   if (t.getPriority() != Thread.NORM_PRIORITY) {
    t.setPriority(Thread.NORM_PRIORITY);
   }
   return t;
  }
 }

 private static class DynamicBlockingQueue extends LinkedBlockingQueue<String> {
  DynamicBlockingQueue(int capacity) {
   super(capacity);
  }

  private volatile boolean done = false;

  public boolean isDone() {
   return done;
  }

  public void setDone(boolean done) {
   this.done = done;
  }
 }
}

  1、taskConstants 动态任务列表

  2、ScheduledTaskRegistrar#addTriggerTask 添加动态周期定时任务,检测动态任务列表的变化

CronTask task = new CronTask(tt, expression);
ScheduledFuture<?> future = registrar.getScheduler().schedule(task.getRunnable(), task.getTrigger());
cronTasks.put(tt.getTaskId(), task);
scheduledFutures.put(tt.getTaskId(), future);

  3、动态创建cron定时任务,拿到ScheduledFuture实例并缓存起来

  4、在刷新任务列表时,通过缓存的ScheduledFuture实例和CronTask实例,来决定是否取消、移除失效的动态定时任务。

v  DynamicTaskTest 动态定时任务测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class DynamicTaskTest {

 @Autowired
 private DynamicTask dynamicTask;

 @Test
 public void test() throws InterruptedException {
  List<DynamicTask.TaskConstant> taskConstans = dynamicTask.getTaskConstants();
  DynamicTask.TaskConstant taskConstant = new DynamicTask.TaskConstant();
  taskConstant.setCron("0/5 * * * * ?");
  taskConstant.setTaskId("test1");
  taskConstans.add(taskConstant);

  DynamicTask.TaskConstant taskConstant1 = new DynamicTask.TaskConstant();
  taskConstant1.setCron("0/5 * * * * ?");
  taskConstant1.setTaskId("test2");
  taskConstans.add(taskConstant1);

  DynamicTask.TaskConstant taskConstant2 = new DynamicTask.TaskConstant();
  taskConstant2.setCron("0/5 * * * * ?");
  taskConstant2.setTaskId("test3");
  taskConstans.add(taskConstant2);

  TimeUnit.SECONDS.sleep(40);
  //移除并添加新的配置
  taskConstans.remove(taskConstans.size() - 1);
  DynamicTask.TaskConstant taskConstant3 = new DynamicTask.TaskConstant();
  taskConstant3.setCron("0/5 * * * * ?");
  taskConstant3.setTaskId("test4");
  taskConstans.add(taskConstant3);
//
  TimeUnit.MINUTES.sleep(50);
 }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 关于SpringBoot获取IOC容器中注入的Bean(推荐)

    一: 注入一个TestUtils类 package com.shop.sell.Utils; import com.shop.sell.dto.CartDTO; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TestUtils { @Bean(name="test

  • SpringBoot 动态定时器的使用方法

    SpringBoot使用定时器使用方法添加@Scheduled注解 设计cron参数即可 package com.clsystem.Comm; import org.springframework.scheduling.annotation.Scheduled; /** * Created by pudding on 2017-11-10.(打卡记录定时任务) */ @Component public class ClockTiming { /** * 定时器 */ @Scheduled(cro

  • 基于spring-boot和docker-java实现对docker容器的动态管理和监控功能[附完整源码下载]

    docker简介 Docker 是一个开源的应用容器引擎,和传统的虚拟机技术相比,Docker 容器性能开销极低,因此也广受开发者喜爱.随着基于docker的开发者越来越多,docker的镜像也原来越丰富,未来各种企业级的完整解决方案都可以直接通过下载镜像拿来即用.因此docker变得越来越重要. 本文目的 本文通过一个项目实例来介绍如果通过docker对外接口来实现对docker容器的管理和监控. 应用场景: 对服务器资源池通过docker进行统一管理,按需分配资源和创建容器,达到资源最大化利

  • Springboot整个Quartz实现动态定时任务的示例代码

    简介 Quartz是一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行.每天凌晨执行.每周五执行等等,还支持分布式调度.本文使用Springboot+Mybatis+Quartz实现对定时任务的增.删.改.查.启用.停用等功能.并把定时任务持久化到数据库以及支持集群. Quartz的3个基本要素 Scheduler:调度器.所有的调度都是由它控制. Trigger: 触发器.决定什么时候来执行任务. JobDetail & Job: JobDetail定义的是任务数据,而真正的

  • 通过Spring Boot配置动态数据源访问多个数据库的实现代码

    之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不多且固定的情况.针对数据库动态增加的情况无能为力. 下面讲的方案能支持数据库动态增删,数量不限. 数据库环境准备 下面一Mysql为例,先在本地建3个数据库用于测试.需要说明的是本方案不限数据库数量,支持不同的数据库部署在不同的服务器上.如图所示db_project_001.d

  • SpringCloud Zuul实现动态路由

    前言 Zuul 是在Spring Cloud Netflix平台上提供动态路由,监控,弹性,安全等边缘服务的框架,是Netflix基于jvm的路由器和服务器端负载均衡器,相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门.本文基于上篇(SpringCloud系列--Ribbon 负载均衡)实现Zuul动态路由 GitHub地址:https://github.com/Netflix/zuul 官方文档:https://cloud.spring.io/spring-cloud-

  • Spring动态加载bean后调用实现方法解析

    前言 在使用Spring的过程中,我们有时候并不想通过xml的方式进行bean的注入,希望在不改变原有的程序结构的基础上添加我们需要的bean,这个时候我们可以利用Spring的spring-beans的jar包里提供的BeanFactoryPostProcessor接口类,通过实现这个接口类,我们可以动态的加载所需要的bean,特别是在引入已经打包在jar里面的程序而没有办法做出修改的情况下,这个接口就为我们提供了及其方便的入口. 因为我是在其他项目的基础上测试的这个接口,所以引入的jar有其

  • Spring boot定时任务的原理及动态创建详解

    v一.前言 定时任务一般是项目中都需要用到的,可以用于定时处理一些特殊的任务.这篇文章主要给大家介绍了关于Spring boot定时任务的原理及动态创建的相关内容,下面来一起看看详细的介绍吧 上周工作遇到了一个需求,同步多个省份销号数据,解绑微信粉丝.分省定时将销号数据放到SFTP服务器上,我需要开发定时任务去解析文件.因为是多省份,服务器.文件名规则.数据规则都不一定,所以要做成可配置是有一定难度的.数据规则这块必须强烈要求统一,服务器.文件名规则都可以从配置中心去读.每新增一个省份的配置,后

  • Spring Boot 多数据源处理事务的思路详解

    目录 1. 思路梳理 2. 代码实践 2.1 案例准备 2.2 开始整活 LoadDataSource.java 3. 总结 首先我先声明一点,本文单纯就是技术探讨,要从实际应用中来说的话,我并不建议这样去玩分布式事务.也不建议这样去玩多数据源,毕竟分布式事务主要还是用在微服务场景下. 好啦,那就不废话了,开整. 1. 思路梳理 首先我们来梳理一下思路. 在上篇文章中,我们是一个微服务,在 A 中分别去调用 B 和 C,当 B 或者 C 有一个执行失败的时候,就去回滚.B 和 C 都是调用远程的

  • spring boot 图片上传与显示功能实例详解

    首先描述一下问题,spring boot 使用的是内嵌的tomcat, 所以不清楚文件上传到哪里去了, 而且spring boot 把静态的文件全部在启动的时候都会加载到classpath的目录下的,所以上传的文件不知相对于应用目录在哪,也不知怎么写访问路径合适,对于新手的自己真的一头雾水. 后面想起了官方的例子,没想到一开始被自己找到的官方例子,后面太依赖百度谷歌了,结果发现只有官方的例子能帮上忙,而且帮上大忙,直接上密码的代码 package hello; import static org

  • Spring Boot实现登录验证码功能的案例详解

    目录 验证码的作用 案例要求 前端页面准备 准备login.html页面 随机验证码工具类 后端控制器 验证码的作用 验证码的作用:可以有效防止其他人对某一个特定的注册用户用特定的程序暴力破解方式进行不断的登录尝试我们其实很经常看到,登录一些网站其实是需要验证码的,比如牛客,QQ等.使用验证码是现在很多网站通行的一种方式,这个问题是由计算机生成并且评判的,但是必须只有人类才能解答,因为计算机无法解答验证码的问题,所以回答出问题的用户就可以被认为是人类.验证码一般用来防止批量注册. 案例要求 验证

  • Spring Boot的listener(监听器)简单使用实例详解

    监听器(Listener)的注册方法和 Servlet 一样,有两种方式:代码注册或者注解注册 1.代码注册方式 通过代码方式注入过滤器 @Bean public ServletListenerRegistrationBean servletListenerRegistrationBean(){ ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean

  • Spring boot进行参数校验的方法实例详解

    Spring boot开发web项目有时候我们需要对controller层传过来的参数进行一些基本的校验,比如非空.整数值的范围.字符串的长度.日期.邮箱等等.Spring支持JSR-303 Bean Validation API,可以方便的进行校验. 使用注解进行校验 先定义一个form的封装对象 class RequestForm { @Size(min = 1, max = 5) private String name; public String getName() { return n

  • Spring Boot 中PageHelper 插件使用配置思路详解

    使用思路 1.引入myabtis和pagehelper依赖 2.yml中配置mybatis扫描和实体类 这2行代码 pageNum:当前第几页 pageSize:显示多少条数据 userList:数据库查询的数据数据列表 PageHelper.startPage(pageNum, pageSize); PageInfo pageInfo = new PageInfo(userList); 最后返回一个pageInfo 对象即可,pageInfo 这个对象中只有数据一些信息,但是,没有成功失败的状

  • Spring Boot设置支持跨域请求过程详解

    现代浏览器出于安全的考虑, HTTP 请求时必须遵守同源策略,否则就是跨域的 HTTP 请求,默认情况下是被禁止的,IP(域名)不同.或者端口不同.协议不同(比如 HTTP.HTTPS)都会造成跨域问题. 一般前端的解决方案有: ① 使用 JSONP 来支持跨域的请求,JSONP 实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的 SRC 不受同源策略约束来跨域获取数据.缺点是需要后端配合输出特定的返回信息. ② 利用反应代理的机制来解决跨域的

  • Spring Boot之@Async异步线程池示例详解

    目录 前言 一. Spring异步线程池的接口类 :TaskExecutor 二.简单使用说明 三.定义通用线程池 1.定义线程池 2.异步方法使用线程池 3.通过xml配置定义线程池 四.异常处理 五.问题 前言 很多业务场景需要使用异步去完成,比如:发送短信通知.要完成异步操作一般有两种: 1.消息队列MQ 2.线程池处理. 我们来看看Spring框架中如何去使用线程池来完成异步操作,以及分析背后的原理. 一. Spring异步线程池的接口类 :TaskExecutor 在Spring4中,

  • Spring Boot 基于注解的 Redis 缓存使用详解

    看文本之前,请先确定你看过上一篇文章<Spring Boot Redis 集成配置>并保证 Redis 集成后正常可用,因为本文是基于上文继续增加的代码. 一.创建 Caching 配置类 RedisKeys.Java package com.shanhy.example.redis; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; import org.springf

随机推荐