1.概念
Quartz的监听器用于当任务调度中你所关注事件发生时,能够及时获取这一事件的通知。类似
于任务执行过程中的邮件、短信类的提醒。Quartz监听器主要有JobListener、
TriggerListener、SchedulerListener三种,顾名思义,分别表示任务、触发器、调度器对应的
监听器。三者的使用方法类似,在开始介绍三种监听器之前,需要明确两个概念:全局监听器
与非全局监听器,二者的区别在于:
全局监听器能够接收到所有的Job/Trigger的事件通知,
而非全局监听器只能接收到在其上注册的Job或Trigger的事件,不在其上注册的Job或Trigger则
不会进行监听。
2.JobListener
任务调度过程中,与任务Job相关的事件包括:job开始要执行的提示; job执行完成的提示。

public interface JobListener {

    /*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/ /**
* <p>
* Get the name of the <code>JobListener</code>.
* </p>
*/
String getName(); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* is about to be executed (an associated <code>{@link Trigger}</code>
* has occurred).
* </p>
*
* <p>
* This method will not be invoked if the execution of the Job was vetoed
* by a <code>{@link TriggerListener}</code>.
* </p>
*
* @see #jobExecutionVetoed(JobExecutionContext)
*/
void jobToBeExecuted(JobExecutionContext context); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* was about to be executed (an associated <code>{@link Trigger}</code>
* has occurred), but a <code>{@link TriggerListener}</code> vetoed it's
* execution.
* </p>
*
* @see #jobToBeExecuted(JobExecutionContext)
*/
void jobExecutionVetoed(JobExecutionContext context); /**
* <p>
* Called by the <code>{@link Scheduler}</code> after a <code>{@link org.quartz.JobDetail}</code>
* has been executed, and be for the associated <code>Trigger</code>'s
* <code>triggered(xx)</code> method has been called.
* </p>
*/
void jobWasExecuted(JobExecutionContext context,
JobExecutionException jobException); }

创建自定义的JobListener
MyJobListener.java

public class MyJobListener implements JobListener {

    @Override
public String getName() {
String name = getClass().getSimpleName();
System.out.println("监听器的名称是:" + name);
return name;
} @Override
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("Job的名称是:" + jobName + " Scheduler在JobDetail将要被执行时调用这个方法");
} @Override
public void jobExecutionVetoed(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("Job的名称是:" + jobName + " Scheduler在JobDetail即将被执行,但又被TriggerListerner否决时会调用该方法");
} @Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("Job的名称是:" + jobName + " Scheduler在JobDetail被执行之后调用这个方法");
}
}

执行调度器
HelloSchedulerDemoJobListener.java

public class HelloSchedulerDemoJobListener {

    public static void main(String[] args) throws Exception {
// 1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJobListener.class).withIdentity("job1", "group1") // 定义该实例唯一标识
.build();
// 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
@SuppressWarnings("static-access")
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") // 定义该实例唯一标识
.startNow() // 马上执行
// .startAt(triggerStartTime) // 针对某个时刻执行
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)) // 每5秒执行一次
.build(); // 4:使用触发器调度任务的执行
scheduler.scheduleJob(job, trigger); // 创建并注册一个全局的Job Listener
scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
// 创建并注册一个指定任务的Job Listener
// scheduler.getListenerManager().addJobListener(new MyJobListener(),
// KeyMatcher.keyEquals(JobKey.jobKey("job1", "group1")));
// 5:开启
scheduler.start();
// 关闭
// scheduler.shutdown();
} }
执行结果:
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail将要被执行时调用这个方法
进行数据库备份操作。当前任务执行的时间:2019-05-12 22:09:15
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail被执行之后调用这个方

3.TriggerListener
任务调度过程中,与触发器Trigger相关的事件包括:触发器触发、触发器未正常触发、触发器
完成等。

public interface TriggerListener {
public String getName();
public void triggerFired(Trigger trigger, JobExecutionContext context);
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);
public void triggerMisfired(Trigger trigger);
public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode);
}

其中:
1) getName方法:用于获取触发器的名称
2) triggerFired方法:当与监听器相关联的Trigger被触发,Job上的execute()方法将被执行时,
Scheduler就调用该方法。
3) vetoJobExecution方法:在 Trigger 触发后,Job 将要被执行时由 Scheduler 调用这个方
法。TriggerListener 给了一个选择去否决 Job 的执行。假如这个方法返回 true,这个 Job 将不
会为此次 Trigger 触发而得到执行。
4) triggerMisfired方法:Scheduler 调用这个方法是在 Trigger 错过触发时。你应该关注此方
法中持续时间长的逻辑:在出现许多错过触发的 Trigger 时,长逻辑会导致骨牌效应。你应当
保持这上方法尽量的小。
5) triggerComplete方法:Trigger 被触发并且完成了 Job 的执行时,Scheduler 调用这个方
法。
示例:
下面的例子简单展示了TriggerListener的使用,其中创建并注册TriggerListener与JobListener
几乎类似。
HelloJobListener.j ava

public class HelloJobListener implements Job {

    @Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// TODO Auto-generated method stub
// 定义时间
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date); // 定义工作任务内容
System.out.println("进行数据库备份操作。当前任务执行的时间:" + dateString); } }
public class MyTriggerListener implements TriggerListener {
private String name; public MyTriggerListener(String name) {
this.name = name;
} @Override
public String getName() {
String name = getClass().getSimpleName();
System.out.println("监听器的名称是:" + name);
return name;
} @Override
public void triggerFired(Trigger trigger, JobExecutionContext context) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 被触发");
} @Override
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 没有被触发");
return false; // true:表示不会执行Job的方法
} @Override
public void triggerMisfired(Trigger trigger) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 错过触发");
} @Override
public void triggerComplete(Trigger trigger, JobExecutionContext context,
CompletedExecutionInstruction triggerInstructionCode) {
String triggerName = trigger.getKey().getName();
System.out.println(triggerName + " 完成之后触发");
} }
public class HelloSchedulerDemoTriggerListener {
@SuppressWarnings("static-access")
public static void main(String[] args) throws Exception {
// 1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJobListener.class).withIdentity("job5", "group5") // 定义该实例唯一标识
.build();
// 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger5", "group5") // 定义该实例唯一标识
.startNow() // 马上执行
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)) // 每5秒执行一次
.build(); // 4:使用触发器调度任务的执行
scheduler.scheduleJob(job, trigger); // 创建并注册一个全局的Trigger Listener
scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simpleTrigger"));
// 创建SchedulerListener
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
// 创建SchedulerListener
// scheduler.getListenerManager().addSchedulerListener(new
// MySchedulerListener());
// 创建并注册一个局部的Trigger Listener
// scheduler.getListenerManager().addTriggerListener(new
// MyTriggerListener("simpleTrigger"),
// KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "group1"))); // 5:开启
scheduler.start();
// 关闭
// scheduler.shutdown();
} }
结果
:(当为true的时候)
trigger1 被触发
trigger1 没有被触发
trigger1 被触发
trigger1 没有被触发
trigger1 被触发
trigger1 没有被触发
=======================================
结果:(当为false的时候)
监听器的名称是:MyTriggerListener
trigger1 被触发
trigger1 没有被触发
进行数据库备份操作。当前任务执行的时间:2019-05-12 22:33:43
trigger1 完成之后触发
=========================================
结果:当监听2个监听器的时候
监听器的名称是:MyTriggerListener
trigger1 被触发
trigger1 没有被触发
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail将要被执行时调用这个方法
进行数据库备份操作。当前任务执行的时间:2019-05-12 22:41:26
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail被执行之后调用这个方法
监听器的名称是:MyTriggerListener
trigger1 完成之后触发

4.SchedulerListener
SchedulerListener会在Scheduler的生命周期中关键事件发生时被调用。与Scheduler有关的
事件包括:增加一个job/trigger,删除一个job/trigger,scheduler发生严重错误,关闭
scheduler等。

public interface SchedulerListener {

    /*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/ /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* is scheduled.
* </p>
*/
void jobScheduled(Trigger trigger); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* is unscheduled.
* </p>
*
* @see SchedulerListener#schedulingDataCleared()
*/
void jobUnscheduled(TriggerKey triggerKey); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link Trigger}</code>
* has reached the condition in which it will never fire again.
* </p>
*/
void triggerFinalized(Trigger trigger); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link Trigger}</code>
* has been paused.
* </p>
*/
void triggerPaused(TriggerKey triggerKey); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a
* group of <code>{@link Trigger}s</code> has been paused.
* </p>
*
* <p>If all groups were paused then triggerGroup will be null</p>
*
* @param triggerGroup the paused group, or null if all were paused
*/
void triggersPaused(String triggerGroup); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link Trigger}</code>
* has been un-paused.
* </p>
*/
void triggerResumed(TriggerKey triggerKey); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a
* group of <code>{@link Trigger}s</code> has been un-paused.
* </p>
*/
void triggersResumed(String triggerGroup); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* has been added.
* </p>
*/
void jobAdded(JobDetail jobDetail); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* has been deleted.
* </p>
*/
void jobDeleted(JobKey jobKey); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* has been paused.
* </p>
*/
void jobPaused(JobKey jobKey); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a
* group of <code>{@link org.quartz.JobDetail}s</code> has been paused.
* </p>
*
* @param jobGroup the paused group, or null if all were paused
*/
void jobsPaused(String jobGroup); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a <code>{@link org.quartz.JobDetail}</code>
* has been un-paused.
* </p>
*/
void jobResumed(JobKey jobKey); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a
* group of <code>{@link org.quartz.JobDetail}s</code> has been un-paused.
* </p>
*/
void jobsResumed(String jobGroup); /**
* <p>
* Called by the <code>{@link Scheduler}</code> when a serious error has
* occurred within the scheduler - such as repeated failures in the <code>JobStore</code>,
* or the inability to instantiate a <code>Job</code> instance when its
* <code>Trigger</code> has fired.
* </p>
*
* <p>
* The <code>getErrorCode()</code> method of the given SchedulerException
* can be used to determine more specific information about the type of
* error that was encountered.
* </p>
*/
void schedulerError(String msg, SchedulerException cause); /**
* <p>
* Called by the <code>{@link Scheduler}</code> to inform the listener
* that it has move to standby mode.
* </p>
*/
void schedulerInStandbyMode(); /**
* <p>
* Called by the <code>{@link Scheduler}</code> to inform the listener
* that it has started.
* </p>
*/
void schedulerStarted(); /**
* <p>
* Called by the <code>{@link Scheduler}</code> to inform the listener
* that it is starting.
* </p>
*/
void schedulerStarting(); /**
* <p>
* Called by the <code>{@link Scheduler}</code> to inform the listener
* that it has shutdown.
* </p>
*/
void schedulerShutdown(); /**
* <p>
* Called by the <code>{@link Scheduler}</code> to inform the listener
* that it has begun the shutdown sequence.
* </p>
*/
void schedulerShuttingdown(); /**
* Called by the <code>{@link Scheduler}</code> to inform the listener
* that all jobs, triggers and calendars were deleted.
*/
void schedulingDataCleared();
}

其中:
1) jobScheduled方法:用于部署JobDetail时调用
2) jobUnscheduled方法:用于卸载JobDetail时调用
3) triggerFinalized方法:当一个 Trigger 来到了再也不会触发的状态时调用这个方法。除非这个 Job 已设置成了持久性,否则它就会从 Scheduler 中移除。
4) triggersPaused方法:Scheduler 调用这个方法是发生在一个 Trigger 或 Trigger 组被暂停时。假如是 Trigger 组的话,triggerName 参数将为 null。
5) triggersResumed方法:Scheduler 调用这个方法是发生成一个 Trigger 或 Trigger 组从暂停中恢复时。假如是 Trigger 组的话,假如是 Trigger 组的话,triggerName 参数将为 null。参数将为 null。
 6) jobsPaused方法:当一个或一组 JobDetail 暂停时调用这个方法。

7)jobsResumed方法:当一个或一组 Job 从暂停上恢复时调用这个方法。假如是一个 Job 组,jobName 参数将为 null。
 8) schedulerError方法:在 Scheduler 的正常运行期间产生一个严重错误时调用这个方法。
9) schedulerStarted方法:当Scheduler 开启时,调用该方法
10)schedulerInStandbyMode方法: 当Scheduler处于StandBy模式时,调用该方法
11)schedulerShutdown方法:当Scheduler停止时,调用该方法
12) schedulingDataCleared方法:当Scheduler中的数据被清除时,调用该方法。
示例:
下面的代码简单描述了如何使用SchedulerListener方法:
HelloJobListener.java

public class HelloJobListener implements Job {

    @Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// TODO Auto-generated method stub
// 定义时间
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = dateFormat.format(date); // 定义工作任务内容
System.out.println("进行数据库备份操作。当前任务执行的时间:" + dateString); } }
public class MySchedulerListener implements SchedulerListener {
@Override
public void jobScheduled(Trigger trigger) {
String jobName = trigger.getJobKey().getName();
System.out.println(jobName + " 完成部署");
} @Override
public void jobUnscheduled(TriggerKey triggerKey) {
System.out.println(triggerKey + " 完成卸载");
} @Override
public void triggerFinalized(Trigger trigger) {
System.out.println("触发器被移除 " + trigger.getJobKey().getName());
} @Override
public void triggerPaused(TriggerKey triggerKey) {
System.out.println(triggerKey + " 正在被暂停");
} @Override
public void triggersPaused(String triggerGroup) {
System.out.println("触发器组 " + triggerGroup + " 正在被暂停");
} @Override
public void triggerResumed(TriggerKey triggerKey) {
System.out.println(triggerKey + " 正在从暂停中恢复");
} @Override
public void triggersResumed(String triggerGroup) {
System.out.println("触发器组 " + triggerGroup + " 正在从暂停中恢复");
} @Override
public void jobAdded(JobDetail jobDetail) {
System.out.println(jobDetail.getKey() + " 添加工作任务");
} @Override
public void jobDeleted(JobKey jobKey) {
System.out.println(jobKey + " 删除工作任务");
} @Override
public void jobPaused(JobKey jobKey) {
System.out.println(jobKey + " 工作任务正在被暂停");
} @Override
public void jobsPaused(String jobGroup) {
System.out.println("工作任务组 " + jobGroup + " 正在被暂停");
} @Override
public void jobResumed(JobKey jobKey) {
System.out.println(jobKey + " 正在从暂停中恢复");
} @Override
public void jobsResumed(String jobGroup) {
System.out.println("工作任务组 " + jobGroup + " 正在从暂停中恢复");
} @Override
public void schedulerError(String msg, SchedulerException cause) {
System.out.println("产生严重错误时调用: " + msg + " " + cause.getUnderlyingException());
} @Override
public void schedulerInStandbyMode() {
System.out.println("调度器在挂起模式下调用");
} @Override
public void schedulerStarted() {
System.out.println("调度器 开启时调用");
} @Override
public void schedulerStarting() {
System.out.println("调度器 正在开启时调用");
} @Override
public void schedulerShutdown() {
System.out.println("调度器 已经被关闭 时调用");
} @Override
public void schedulerShuttingdown() {
System.out.println("调度器 正在被关闭 时调用");
} @Override
public void schedulingDataCleared() {
System.out.println("调度器的数据被清除时调用");
} }
public class HelloSchedulerDemoSchedulerListener {
public static void main(String[] args) throws Exception {
// 1:从工厂中获取任务调度的实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); // 2:定义一个任务调度实例,将该实例与HelloJob绑定,任务类需要实现Job接口
JobDetail job = JobBuilder.newJob(HelloJobListener.class).withIdentity("job1", "group1") // 定义该实例唯一标识
.build();
// 3:定义触发器 ,马上执行, 然后每5秒重复执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") // 定义该实例唯一标识
.startNow() // 马上执行
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5)) // 每5秒执行一次
.build(); // 4:使用触发器调度任务的执行
Date scheduleJob = scheduler.scheduleJob(job, trigger); // 创建SchedulerListener
scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("simpleTrigger"));
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener()); // 移除对应的SchedulerListener
// scheduler.getListenerManager().removeSchedulerListener(new
// MySchedulerListener()); // 5:开启
scheduler.start();
// 延迟7秒后关闭
Thread.sleep(7000);
// 关闭
scheduler.shutdown();
} }
调度
器 正在开启时调用
23:01:03,174 INFO QuartzScheduler:547 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED started.
调度器 开启时调用
进行数据库备份操作。当前任务执行的时间:2019-05-12 23:01:03
进行数据库备份操作。当前任务执行的时间:2019-05-12 23:01:08
23:01:10,178 INFO QuartzScheduler:666 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
23:01:10,179 INFO QuartzScheduler:585 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED paused.
调度器在挂起模式下调用
调度器 正在被关闭 时调用
调度器 已经被关闭 时调用
23:01:10,181 INFO QuartzScheduler:740 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.
调度器 正在开启时调用
23:10:21,011 INFO QuartzScheduler:547 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED started.
调度器 开启时调用
监听器的名称是:MyTriggerListenertrigger1 被触发
trigger1 没有被触发
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail将要被执行时调用这个方法
进行数据库备份操作。当前任务执行的时间:2019-05-12 23:10:21
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail被执行之后调用这个方法
监听器的名称是:MyTriggerListener
trigger1 完成之后触发
=======================================
监听器的名称是:MyTriggerListener
trigger1 被触发
trigger1 没有被触发
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail将要被执行时调用这个方法
进行数据库备份操作。当前任务执行的时间:2019-05-12 23:10:26
监听器的名称是:MyJobListener
Job的名称是:job1 Scheduler在JobDetail被执行之后调用这个方法
监听器的名称是:MyTriggerListener
trigger1 完成之后触发
========================================
23:10:28,021 INFO QuartzScheduler:666 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
23:10:28,021 INFO QuartzScheduler:585 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED paused.
调度器在挂起模式下调用
调度器 正在被关闭 时调用
调度器 已经被关闭 时调用
23:10:28,022 INFO QuartzScheduler:740 - Scheduler
DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete

最新文章

  1. 作弊Q-百威
  2. Roll A Ball
  3. 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByTagName
  4. &lt;转&gt;如何进行code review
  5. php中array_flip数组翻转
  6. ~0u &gt;&gt; 1
  7. [SQL] 不知道是什么存储过程
  8. centos Supervisor
  9. OCI下报出的数据库重账错
  10. php 加密解密方法2
  11. Ubuntu上架设PPPoE Server
  12. linux平台的office文档转pdf(程序员的菜)
  13. MySQL中的基本知识
  14. Weblogic Exception in AppMerge flows&#39; progression
  15. 2017 6 2php用PDO链接数据库前测试
  16. win10系统安装了多个版本的JDK如何切换
  17. 【译】第11节---数据注解-TimeStamp
  18. Go 定长的数组
  19. Codeforces Round #323 (Div. 2) C. GCD Table map
  20. angluarjs ng-repeat 行号

热门文章

  1. js多张图片合成一张图,canvas(海报图,将二维码和背景图合并) -----vue
  2. python 并发编程 基于gevent模块 协程池 实现并发的套接字通信
  3. windows编程 使用C++实现多线程类
  4. excel常用公式--时间序列类
  5. awk 命令使用指南
  6. dp常见优化方法
  7. Quartz的job中注入的services接口为空的解决办法
  8. Jinja2 中文手册
  9. JS拖拽系列--多元素拖拽,面向对象,es6拖拽
  10. 76. Minimum Window Substring (JAVA)