Springboot定時任務原理及如何動態創建定時任務

一、前言

  上周工作遇到了一個需求,同步多個省份銷號數據,解綁微信粉絲。分省定時將銷號數據放到SFTP服務器上,我需要開發定時任務去解析文件。因為是多省份,服務器、文件名規則、數據規則都不一定,所以要做成可配置是有一定難度的。數據規則這塊必須強烈要求統一,服務器、文件名規則都可以從配置中心去讀。每新增一個省份的配置,后臺感知到后,動態生成定時任務。

二、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。

三、主要看一下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);elseensurePrestart();}
}

  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 runWorkerthis.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方法也可能拋出異常,也可能使當前線程死掉。

四、動態創建定時任務

  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-";}@Overridepublic 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、自定義線程池工廠類

  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();@Overridepublic 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;}@PreDestroypublic 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;}@Overridepublic void run() {//設置隊列大小10LOGGER.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;}@Overridepublic 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-";}@Overridepublic 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實例,來決定是否取消、移除失效的動態定時任務。

  DynamicTaskTest 動態定時任務測試類

@RunWith(SpringRunner.class)
@SpringBootTest
public class DynamicTaskTest {@Autowiredprivate DynamicTask dynamicTask;@Testpublic 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);}
}

?

轉載于:https://www.cnblogs.com/hujunzheng/p/10353390.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/531202.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/531202.shtml
英文地址,請注明出處:http://en.pswp.cn/news/531202.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

轉載:ThreadPoolExecutor 源碼閱讀

前言 之前研究了一下如何使用ScheduledThreadPoolExecutor動態創建定時任務(Springboot定時任務原理及如何動態創建定時任務)&#xff0c;簡單了解了ScheduledThreadPoolExecutor相關源碼。今天看了同學寫的ThreadPoolExecutor 的源碼解讀&#xff0c;甚是NB&#xff0c;必須轉…

Spring BPP中優雅的創建動態代理Bean

一、前言 本文章所講并沒有基于Aspectj&#xff0c;而是直接通過Cglib以及ProxyFactoryBean去創建代理Bean。通過下面的例子&#xff0c;可以看出Cglib方式創建的代理Bean和ProxyFactoryBean創建的代理Bean的區別。 二、基本測試代碼 測試實體類&#xff0c;在BPP中創建BppTest…

使用pdfBox實現pdf轉圖片,解決中文方塊亂碼等問題

一、引入依賴 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.13</version> </dependency> <dependency><groupId>org.apache.pdfbox</groupId><artif…

Spring異步調用原理及SpringAop攔截器鏈原理

一、Spring異步調用底層原理 開啟異步調用只需一個注解EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {/*** Indicate the async annotation type to be detec…

線程池優化之充分利用線程池資源

一、前言 最近做了電子發票的需求&#xff0c;分省開票接口和發票下載接口都有一定的延遲。為了完成開票后自動將發票插入用戶微信卡包&#xff0c;目前的解決方案是利用線程池&#xff0c;將開票后插入卡包的任務&#xff08;輪詢分省發票接口&#xff0c;直到獲取到發票相關信…

Spring MVC源碼——Root WebApplicationContext

Spring MVC源碼——Root WebApplicationContext 打算開始讀一些框架的源碼,先拿 Spring MVC 練練手,歡迎點擊這里訪問我的源碼注釋, SpringMVC官方文檔一開始就給出了這樣的兩段示例: WebApplicationInitializer示例: public class MyWebApplicationInitializer implements Web…

Spring MVC源碼——Servlet WebApplicationContext

上一篇筆記(Spring MVC源碼——Root WebApplicationContext)中記錄了下 Root WebApplicationContext 的初始化代碼.這一篇來看 Servlet WebApplicationContext 的初始化代碼 DispatcherServlet 是另一個需要在 web.xml 中配置的類, Servlet WebApplicationContext 就由它來創建…

Springboot源碼——應用程序上下文分析

前兩篇(Spring MVC源碼——Root WebApplicationContext 和 Spring MVC源碼——Servlet WebApplicationContext)講述了springmvc項目創建上下文的過程&#xff0c;這一篇帶大家了解一下springboot項目創建上下文的過程。 SpringApplication引導類 SpringApplication類用于啟動或…

基于zookeeper實現分布式配置中心(一)

最近在學習zookeeper&#xff0c;發現zk真的是一個優秀的中間件。在分布式環境下&#xff0c;可以高效解決數據管理問題。在學習的過程中&#xff0c;要深入zk的工作原理&#xff0c;并根據其特性做一些簡單的分布式環境下數據管理工具。本文首先對zk的工作原理和相關概念做一下…

基于zookeeper實現分布式配置中心(二)

上一篇&#xff08;基于zookeeper實現分布式配置中心&#xff08;一&#xff09;&#xff09;講述了zookeeper相關概念和工作原理。接下來根據zookeeper的特性&#xff0c;簡單實現一個分布式配置中心。 配置中心的優勢 1、各環境配置集中管理。 2、配置更改&#xff0c;實時推…

Redis分布式鎖實戰

背景 目前開發過程中&#xff0c;按照公司規范&#xff0c;需要依賴框架中的緩存組件。不得不說&#xff0c;做組件的大牛對CRUD操作的封裝&#xff0c;連接池、緩存路由、緩存安全性的管控都處理的無可挑剔。但是有一個小問題&#xff0c;該組件沒有對分布式鎖做實現&#xff…

基于RobotFramework實現自動化測試

Java robotframework seleniumlibrary 使用Robot Framework Maven Plugin&#xff08;http://robotframework.org/MavenPlugin/&#xff09;執行自動化測試chromedriver下載&#xff1a; http://chromedriver.storage.googleapis.com/index.htmlchromedriver和chrome版本對應…

Springboot國際化信息(i18n)解析

國際化信息理解 國際化信息也稱為本地化信息 。 Java 通過 java.util.Locale 類來表示本地化對象&#xff0c;它通過 “語言類型” 和 “國家/地區” 來創建一個確定的本地化對象 。舉個例子吧&#xff0c;比如在發送一個具體的請求的時候&#xff0c;在header中設置一個鍵值對…

看了就知道為什么別人C語言學習效率那么高了

談及C語言&#xff0c;我想C語言功能強大都應該知道、應用廣泛&#xff0c;一旦掌握了后&#xff0c;你就可以理直氣壯地對他人說“我是電腦高手&#xff01;”&#xff0c;而且以后若是再自學其他語言就顯得輕而易舉了。憂慮的是&#xff0c;C語言般博大精深&#xff0c;太難學…

C語言一看就能上手的干貨!你確定你不來看嗎?

本地環境設置 如果您想要設置 C 語言環境&#xff0c;您需要確保電腦上有以下兩款可用的軟件&#xff0c;文本編輯器和 C 編譯器。 文本編輯器 這將用于輸入您的程序。文本編輯器包括 Windows Notepad、OS Edit command、Brief、Epsilon、EMACS 和 vim/vi。文本編輯器的名稱…

C語言爆炸干貨,小白你還不來看看嘛!

①&#xff1a;數據類型 int(整型)&#xff0c;short int(短整型)&#xff0c;long int(長整型)&#xff0c; char(字符型)&#xff0c;float&#xff08;單精度浮點型&#xff09; double&#xff08;雙精度浮點型&#xff09; C語言編程交流群815393895 ②&#xff1a;邏…

10萬碼農五年的C語言筆記!你現在知道別人為什么這么優秀了嗎?

c語言對許多同學來說確實是一門比較難學的課程&#xff0c;不僅抽象&#xff0c;而且繁瑣&#xff0c;但這又是一門不得不學的課程。前兩節可能還有興致聽一聽&#xff0c;然而&#xff0c;再過幾節課就是一臉蒙比。憑空要想出一道題的算法和程序&#xff0c;根本無從下手。 所…

C語言從來都沒有過時,你大爺終究是你大爺

直到今天&#xff0c;有人在喊C語言過時的語言&#xff0c;還有什么值得學習的&#xff0c;現在看Python&#xff0c;PHP等語言現在都很容易用&#xff0c;誰還在學習老C語言&#xff0c;其實這是真的嗎&#xff1f;作者下載了兩種語言的源代碼作為下載器。由于空間的限制&…

C語言超級瑪麗菜單模塊源碼

C語言是面向過程的&#xff0c;而C&#xff0b;&#xff0b;是面向對象的 C和C的區別&#xff1a; C是一個結構化語言&#xff0c;它的重點在于算法和…

C語言使用函數必須知道的3點注意事項!

C語言是面向過程的&#xff0c;而C&#xff0b;&#xff0b;是面向對象的 C和C的區別&#xff1a; C是一個結構化語言&#xff0c;它的重點在于算法和數據結構。C程序的設計首要考慮的是如何通過一個過程&#xff0c;對輸入&#xff08;或環境條件&#xff09;進行運算處理得…