原创/朱季谦

本文需要一定责任链模式的基础,主要分成三部分讲解:

一、简单理解责任链模式概念

二、Activiti工作流里责任链模式的建立

三、Activiti工作流里责任链模式的应用

一、简单理解责任链模式概念

网上关于责任链模式的介绍很多,菜鸟教程上是这样说的:责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

这个概念术语比较抽象。

我曾经在深入理解Spring Security授权机制原理 一文中提到Spring Security在授权过程中有使用到过滤器的概念,过滤器链就像一条铁链,中间的每个过滤器都包含对另一个过滤器的引用,从而把相关的过滤器链接起来,像一条链的样子。这时请求线程就如蚂蚁一样,会沿着这条链一直爬过去-----即,通过各过滤器调用另一个过滤器引用方法chain.doFilter(request, response),实现一层嵌套一层地将请求传递下去,当该请求传递到能被处理的的过滤器时,就会被处理,处理完成后转发返回。通过过滤器链,可实现在不同的过滤器当中对请求request做处理,且过滤器之间彼此互不干扰。

整个流程大致如下:

这个过滤器链的概念,其实就是责任链设计模式在Spring Security中的体现。

摘录一段网上关于职责链模式介绍,其主要包含以下角色:

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

二、Activiti工作流里责任链模式的创建

最近在研究Activiti工作流框架,发现其所有实现都是采用命令模式实现,而命令模式当中的Invoker角色又是采用拦截器链式模式,即类似上面提到的过滤器链,即设计模式里的责任链模式。

这里的Activiti工作流版本是6.0。

CommandInterceptor是一个拦截器接口,包含三个方法:

  • setNext()方法是在初始化时,设置每个拦截器对象中包含了下一个拦截器对象,最后形成一条拦截器链;
  • getNext()可在每个拦截器对象中调用下一个拦截器对象;
  • execute()是每个拦截器对请求的处理。若在上一个拦截器链式里不能处理该请求话,就会通过next.execute(CommandConfig var1, Command var2)将请求传递到下一个拦截器做处理,类似上面过滤器里调用下一个过滤器的chain.doFilter(request, response)方法,将请求进行传递;
  1. public interface CommandInterceptor {
  2. <T> T execute(CommandConfig var1, Command<T> var2);
  3. CommandInterceptor getNext();
  4. void setNext(CommandInterceptor var1);
  5. }

抽象类AbstractCommandInterceptor实现了CommandInterceptor拦截器接口,在责任链模式当中充当抽象处理者(Handler)角色。该类最主要的属性是 protected CommandInterceptor next,在同一包下,直接通过next即可调用下一个拦截器对象。

  1. public abstract class AbstractCommandInterceptor implements CommandInterceptor {
  2. protected CommandInterceptor next;
  3. public AbstractCommandInterceptor() {
  4. }
  5. public CommandInterceptor getNext() {
  6. return this.next;
  7. }
  8. public void setNext(CommandInterceptor next) {
  9. this.next = next;
  10. }
  11. }

接下来,将会分析拦截器链是如何初始化与工作的。

SpringBoot集成Activiti配置如下:

  1. @Configuration
  2. public class SpringBootActivitiConfig {
  3. @Bean
  4. public ProcessEngine processEngine() {
  5. ProcessEngineConfiguration pro = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
  6. pro.setJdbcDriver("com.mysql.jdbc.Driver");
  7. pro.setJdbcUrl("xxxx");
  8. pro.setJdbcUsername("xxxx");
  9. pro.setJdbcPassword("xxx");
  10. //避免发布的图片和xml中文出现乱码
  11. pro.setActivityFontName("宋体");
  12. pro.setLabelFontName("宋体");
  13. pro.setAnnotationFontName("宋体");
  14. //数据库更更新策略
  15. pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
  16. return pro.buildProcessEngine();
  17. }
  18. }

这时,启动项目后,pro.buildProcessEngine()这行代码会初始化Activiti框架,进入里面,会发现它有三种实现,默认是第二种,即ProcessEngineConfigurationImpl。

点进去,Activiti框架具体构建buildProcessEngine方法如下,其中 this.init()的作用是环境初始化,包括配置设置、JDBC连接、bean装载等的:

  1. public ProcessEngine buildProcessEngine() {
  2. this.init();
  3. ProcessEngineImpl processEngine = new ProcessEngineImpl(this);
  4. if (this.isActiviti5CompatibilityEnabled && this.activiti5CompatibilityHandler != null) {
  5. Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());
  6. this.activiti5CompatibilityHandler.getRawProcessEngine();
  7. }
  8. this.postProcessEngineInitialisation();
  9. return processEngine;
  10. }

在this.init()方法里,涉及到责任链模式初始化的方法是this.initCommandExecutors(),里面详情如下:

  1. public void initCommandExecutors() {
  2. this.initDefaultCommandConfig();
  3. this.initSchemaCommandConfig();
  4. //初始化命令调用器
  5. this.initCommandInvoker();
  6. //List存放进涉及到的拦截器
  7. this.initCommandInterceptors();
  8. //初始化命令执行器
  9. this.initCommandExecutor();
  10. }

这里只需要关注最后三个方法——

  1. this.initCommandInvoker()

    initCommandInvoker()初始化构建了一个CommandInvoker拦截器,它继承上边提到的拦截器抽象类AbstractCommandInterceptor。这个拦截器在整条过滤器链中是最重要和关键,它排在了整条链的最后,其实,它才是最终执行请求的,前边几个拦截器都是传递请求而已。

    1. public void initCommandInvoker() {
    2. if (this.commandInvoker == null) {
    3. if (this.enableVerboseExecutionTreeLogging) {
    4. this.commandInvoker = new DebugCommandInvoker();
    5. } else {
    6. //初始化执行该行代码
    7. this.commandInvoker = new CommandInvoker();
    8. }
    9. }
    10. }

    这里 new CommandInvoker()一个对象,然后将地址复制给this.commandInvoker对象引用,注意,该引用将会用在接下来的initCommandInterceptors()方法里——

  2. this.initCommandInterceptors();

    initCommandInterceptors方法主要作用是创建一个List集合,然后将需要用到的拦截器都保存到该List集合里——

    1. public void initCommandInterceptors() {
    2. if (this.commandInterceptors == null) {
    3. this.commandInterceptors = new ArrayList();
    4. if (this.customPreCommandInterceptors != null) {
    5. //用户自定义前置拦截器
    6. this.commandInterceptors.addAll(this.customPreCommandInterceptors);
    7. }
    8. //框架自带默认的拦截器
    9. this.commandInterceptors.addAll(this.getDefaultCommandInterceptors());
    10. if (this.customPostCommandInterceptors != null) {
    11. this.commandInterceptors.addAll(this.customPostCommandInterceptors);
    12. }
    13. //命令调用器,在拦截器链最后一个
    14. this.commandInterceptors.add(this.commandInvoker);
    15. }
    16. }

    this.getDefaultCommandInterceptors()的代码如下:

    1. public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
    2. List<CommandInterceptor> interceptors = new ArrayList();
    3. //日志拦截器
    4. interceptors.add(new LogInterceptor());
    5. CommandInterceptor transactionInterceptor = this.createTransactionInterceptor();
    6. if (transactionInterceptor != null) {
    7. interceptors.add(transactionInterceptor);
    8. }
    9. //
    10. if (this.commandContextFactory != null) {
    11. interceptors.add(new CommandContextInterceptor(this.commandContextFactory, this));
    12. }
    13. //事务拦截器
    14. if (this.transactionContextFactory != null) {
    15. interceptors.add(new TransactionContextInterceptor(this.transactionContextFactory));
    16. }
    17. return interceptors;
    18. }

    可见,方法里的 this.commandInterceptors 就是一个专门储存拦截器对象的List集合——

    1. protected List<CommandInterceptor> commandInterceptors;

    这里只需要重点关注this.commandInterceptors.add(this.commandInvoker)这行代码,就是将上边创建的CommandInvoker拦截器对象存储到List里,它是放在initCommandInterceptors()方法最后,某种程度也就意味着,这个拦截器在整条链当中处在最后面的位置。

    执行完该this.initCommandInterceptors()方法后,就可获取到所有的拦截器对象,到这一步时,各拦截器还是互相独立的,仍无法通过next()来进行调用传递,那么,究竟是如何将它们串起来形成一条链呢?

    接下来的this.initCommandExecutor()方法,就是实现将各拦截器串起来形成一条长链。

  3. this.initCommandExecutor();

该方法有两个作用,一个是生成Interceptor拦截器链,一个是创建命令执行器commandExecutor。

  1. public void initCommandExecutor() {
  2. if (this.commandExecutor == null) {
  3. CommandInterceptor first = this.initInterceptorChain(this.commandInterceptors);
  4. this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);
  5. }
  6. }

this.initInterceptorChain(this.commandInterceptors)是将集合里的拦截器初始化生成一条拦截器链,先循环获取List集合里的拦截器对象chain.get(i),然后通过setNext()方法在该拦截器对象chain.get(i)里设置下一个拦截器引用,这样,就可实现责任链里所谓每个接收者都包含对另一个接收者的引用的功能。

  1. public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
  2. if (chain != null && !chain.isEmpty()) {
  3. for(int i = 0; i < chain.size() - 1; ++i) {
  4. ((CommandInterceptor)chain.get(i)).setNext((CommandInterceptor)chain.get(i + 1));
  5. }
  6. return (CommandInterceptor)chain.get(0);
  7. } else {
  8. throw new ActivitiException("invalid command interceptor chain configuration: " + chain);
  9. }
  10. }

那么,这条拦截器链当中,都有哪些拦截器呢?

直接debug到这里,可以看到,总共有4个拦截器对象,按照顺序排,包括LogInterceptor,CommandContextInterceptor,TransactionContextInterceptor,CommandInvoker(在命令模式里,该类相当Invoker角色)。这四个拦截器对象在责任链模式当中充当了具体处理者(Concrete Handler)角色。

责任链模式里剩余客户类(Client)角色应该是命令执行器this.commandExecutor。

因此,工作流引擎当中的责任链模式结构图如下:

组成一条拦截器链如下图所示——

生成拦截器链后,会返回一个(CommandInterceptor)chain.get(0),即拦截器LogInterceptor,为什么只返回第一个拦截器呢,这是一个很巧妙的地方,因为该拦截器里已经一层一层地嵌套进其他拦截器了,因此,只需要返回第一个拦截器,赋值给first即可。

接下来,就会创建命令执行器——

  1. this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);

这个命令执行器是整个引擎的底层灵魂,通过它,可以实现责任链模式与命令模式——

拦截器链初始化介绍完成后,接下来开始介绍拦截器链在引擎里的应用方式。

三、Activiti工作流里责任链模式的应用

Activiti引擎的各操作方法其底层基本都是以命令模式来实现的,即调用上面创建的命令执行器this.commandExecutor的execute方法来实现的,例如自动生成28张数据库表的方法,就是通过命令模式去做具体实现的——

  1. this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

进入到commandExecutor方法里,会发现前边new CommandExecutorImpl(this.getDefaultCommandConfig(), first)建立命令执行器时,已将配置对象和嵌套其他拦截器的LogInterceptor拦截器对象,通过构造器CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first)生成对象时,传参赋值给了相应的对象属性,其中first引用指向LogInterceptor,即拦截器链上的第一个拦截器——

  1. public class CommandExecutorImpl implements CommandExecutor {
  2. protected CommandConfig defaultConfig;
  3. protected CommandInterceptor first;
  4. public CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first) {
  5. this.defaultConfig = defaultConfig;
  6. this.first = first;
  7. }
  8. public CommandInterceptor getFirst() {
  9. return this.first;
  10. }
  11. public void setFirst(CommandInterceptor commandInterceptor) {
  12. this.first = commandInterceptor;
  13. }
  14. public CommandConfig getDefaultConfig() {
  15. return this.defaultConfig;
  16. }
  17. public <T> T execute(Command<T> command) {
  18. return this.execute(this.defaultConfig, command);
  19. }
  20. public <T> T execute(CommandConfig config, Command<T> command) {
  21. return this.first.execute(config, command);
  22. }
  23. }

当引擎执行this.commandExecutor.execute(xxx,xxx))类似方法时,其实是执行了this.first.execute(config, command)方法,这里的this.first在构建命令执行器时是通过LogInterceptor传进来的,因此,执行代码其实是调用了LogInterceptor内部的execute()方法,也就是说,开始拦截器链上的第一个LogInterceptor拦截器传递方法execute()请求——

进入到拦截器链上的第一个拦截器LogInterceptor。

根据其内部代码可以看出,这是一个跟日志有关的拦截器,内部并没有多少增强功能,只是做了一个判断是否需要debug日志打印。若需要,则进行debug打印,若不需要,直接进入到 if (!log.isDebugEnabled()) 为true的作用域内部,进而执行this.next.execute(config, command)用以将请求传递给下一个拦截器做处理。

  1. public class LogInterceptor extends AbstractCommandInterceptor {
  2. private static Logger log = LoggerFactory.getLogger(LogInterceptor.class);
  3. public LogInterceptor() {
  4. }
  5. public <T> T execute(CommandConfig config, Command<T> command) {
  6. if (!log.isDebugEnabled()) {
  7. return this.next.execute(config, command);
  8. } else {
  9. log.debug("\n");
  10. log.debug("--- starting {} --------------------------------------------------------", command.getClass().getSimpleName());
  11. Object var3;
  12. try {
  13. var3 = this.next.execute(config, command);
  14. } finally {
  15. log.debug("--- {} finished --------------------------------------------------------", command.getClass().getSimpleName());
  16. log.debug("\n");
  17. }
  18. return var3;
  19. }
  20. }
  21. }

这里有一个小地方值得稍微打断说下,就这个 if (!log.isDebugEnabled())判断。众生周知,若集成第三方日志插件如logback之类,若其配置里去除debug的打印,即时代码里 存在log.debug("xxxxx")也不会打印到控制台,那么,这里增加一个判断 if (!log.isDebugEnabled())是否多次一举呢?

事实上,这里并非多此一举,增加这个判断,是可以提升代码执行效率的。因为log.debug("xxxxx")里的字符串拼接早于log.debug("xxxxx")方法执行的,也就是说,即使该log.debug("xxxxx")不会打印,但其内部的字符串仍然会进行拼接,而拼接,是需要时间的,虽然很细微,但同样属于影响性能范畴内的。因此,增加一个if判断,若无需要打印debug日志时,那么就无需让其内部的字符串进行自动拼接。

这是一个很小的知识点,但面试过程中其实是有可能会遇到这类与日志相关的面试题的。

接下来,让我们继续回到拦截器链的传递上来。

LogInterceptor拦截器调用this.next.execute(config, command),意味着将请求传递到下一个拦截器上进行处理,根据前边分析,可知下一个拦截器是CommandContextInterceptor,根据代码大概可知,这个拦截器内主要是获取上下文配置对象和信息相关的,这些都是在工作流引擎初始化时生成的,它们被保存在Stack栈里,具体都保存了哪些信息暂不展开分析——

  1. public class CommandContextInterceptor extends AbstractCommandInterceptor {
  2. ......
  3. public <T> T execute(CommandConfig config, Command<T> command) {
  4. CommandContext context = Context.getCommandContext();
  5. boolean contextReused = false;
  6. if (config.isContextReusePossible() && context != null && context.getException() == null) {
  7. contextReused = true;
  8. context.setReused(true);
  9. } else {
  10. context = this.commandContextFactory.createCommandContext(command);
  11. }
  12. try {
  13. Context.setCommandContext(context);
  14. Context.setProcessEngineConfiguration(this.processEngineConfiguration);
  15. if (this.processEngineConfiguration.getActiviti5CompatibilityHandler() != null) {
  16. Context.setActiviti5CompatibilityHandler(this.processEngineConfiguration.getActiviti5CompatibilityHandler());
  17. }
  18. //继续将命令请求传递到下一个拦截器
  19. Object var5 = this.next.execute(config, command);
  20. return var5;
  21. } catch (Exception var31) {
  22. context.exception(var31);
  23. } finally {
  24. ......
  25. }
  26. return null;
  27. }
  28. }

CommandContextInterceptor拦截器没有对命令请求做处理,它继续将请求传递到下一个拦截器TransactionContextInterceptor,根据名字就大概可以猜到,这个拦截器主要是增加与事务有关的功能——

  1. public <T> T execute(CommandConfig config, Command<T> command) {
  2. CommandContext commandContext = Context.getCommandContext();
  3. boolean isReused = commandContext.isReused();
  4. Object var9;
  5. try {
  6. if (this.transactionContextFactory != null && !isReused) {
  7. TransactionContext transactionContext = this.transactionContextFactory.openTransactionContext(commandContext);
  8. Context.setTransactionContext(transactionContext);
  9. commandContext.addCloseListener(new TransactionCommandContextCloseListener(transactionContext));
  10. }
  11. var9 = this.next.execute(config, command);
  12. } finally {
  13. ......
  14. }
  15. return var9;
  16. }

TransactionContextInterceptor拦截器同样没有对命令请求做处理,而是继续传递到下一个拦截器,也就是最后一个拦截器CommandInvoker,根据名字可以大概得知,这是一个与命令请求有关的拦截器,传递过来的请求将会在这个拦截器里处理——

  1. public class CommandInvoker extends AbstractCommandInterceptor {
  2. ......
  3. public <T> T execute(CommandConfig config, final Command<T> command) {
  4. final CommandContext commandContext = Context.getCommandContext();
  5. commandContext.getAgenda().planOperation(new Runnable() {
  6. public void run() {
  7. commandContext.setResult(command.execute(commandContext));
  8. }
  9. });
  10. this.executeOperations(commandContext);
  11. if (commandContext.hasInvolvedExecutions()) {
  12. Context.getAgenda().planExecuteInactiveBehaviorsOperation();
  13. this.executeOperations(commandContext);
  14. }
  15. return commandContext.getResult();
  16. }
  17. }

进入到其内部,可以发现,这里没有再继续调用this.next.execute(config, command)这样的请求进行传递,而是直接执行command.execute(commandContext),然后将返回值进行返回,其中,command是请求参数当中的第二个参数,让我们回过头看下该请求案例最开始的调用——

  1. this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

这里的第二个参数是new SchemaOperationsProcessEngineBuild(),不妨进入到SchemaOperationsProcessEngineBuild类中,是吧,其内部同样有一个execute方法——

  1. public final class SchemaOperationsProcessEngineBuild implements Command<Object> {
  2. public SchemaOperationsProcessEngineBuild() {
  3. }
  4. public Object execute(CommandContext commandContext) {
  5. DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
  6. if (dbSqlSession != null) {
  7. dbSqlSession.performSchemaOperationsProcessEngineBuild();
  8. }
  9. return null;
  10. }
  11. }

可见,CommandInvoker拦截器内部执行command.execute(commandContext),就相当于执行了new SchemaOperationsProcessEngineBuild().execute(commandContext),也就是——

  1. public Object execute(CommandContext commandContext) {
  2. DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
  3. if (dbSqlSession != null) {
  4. dbSqlSession.performSchemaOperationsProcessEngineBuild();
  5. }
  6. return null;
  7. }

这是一种命令模式的实现。

本文主要是分析责任链模式在Activiti框架中的实践,故暂不展开分析框架中的其他设计模式,有兴趣的童鞋可以自行深入研究,在Activiti框架当中,其操作功能底层基本都是以命令模式来实现的。

至此,就大概分析完了责任链模式在Activiti框架的创建和应用,学习完这块内容,我对责任链模式有了更好理解,相对于看网上那些简单以小例子来介绍设计模式的方法,我更喜欢去深入框架当中学习其设计模式,这更能让我明白,这种设计模式在什么场景下适合应用,同时,能潜移默化地影响到我,让我在设计系统架构时,能明白各设计模式的落地场景具体都是怎样的。

Activiti工作流学习笔记(四)——工作流引擎中责任链模式的建立与应用原理的更多相关文章

  1. mina中责任链模式的实现

    一.mina的框架回顾 责任链模式在mina中有重要的作用,其中Filter机制就是基于责任链实现的. 从上图看到消息的接受从IoService层先经过Filter层过滤处理后最后交给IoHander ...

  2. 【2016-10-12】【坚持学习】【Day3】【责任链模式】

    今天学习责任链模式 例子: 采购审批系统 采购单需要经过不同人审批 采购价格<500 部门经理审批 采购价格<1000 部门主任审批 采购价格<2000 副总审批 采购价格<5 ...

  3. es6 class中责任链模式与AOP结合

    责任链模式大家应该都清楚,比如JS中的冒泡,Java中的拦截器.过滤器,都运用到了责任链模式. 可以看我之前的文章介绍责任链的:https://www.cnblogs.com/wuguanglin/p ...

  4. JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(5):责任链模式、观察者模式

    一.责任链模式.观察者模式 1.责任链模式:当一个对象在一条链上被多个拦截器处理(烂机器也可以选择不拦截处理它)时,我们把这样的设计模式称为责任链模式,它用于一个对象在多个角色中传递的场景.   2. ...

  5. CodeIgniter学习笔记四:CI中的URL相关函数,路由,伪静态,去掉index.php

    一.URL相关函数 1.加载url模块 加载url有两种方式: a.自动加载:在 application/config/autoload.php 中开启 $autoload['helper'] = a ...

  6. python 学习笔记(四) 统计序列中元素出现的频度(即次数)

    案例一:在某随机序例中,找到出现频度最高的3个元素,它们出现的次数是多少? from random import randint # 利用列表解析器生成随机序列,包含有30个元素 data = [ra ...

  7. 二十四种设计模式:责任链模式(Chain of Responsibility Pattern)

    责任链模式(Chain of Responsibility Pattern) 介绍为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求.将这些对象连成一条链,并沿着这条链传递该请求,直 ...

  8. 设计模式学习之责任链模式(Chain of Responsibility,行为型模式)(22)

    参考:http://www.cnblogs.com/zhili/p/ChainOfResponsibity.html 一.引言 在现实生活中,有很多请求并不是一个人说了就算的,例如面试时的工资,低于1 ...

  9. Activiti工作流学习笔记(三)——自动生成28张数据库表的底层原理分析

    原创/朱季谦 我接触工作流引擎Activiti已有两年之久,但一直都只限于熟悉其各类API的使用,对底层的实现,则存在较大的盲区. Activiti这个开源框架在设计上,其实存在不少值得学习和思考的地 ...

随机推荐

  1. taro render html

    taro render html html = `<h1 style='color: red'>Wallace is way taller than other reporters.< ...

  2. Python爬虫_qq音乐示例代码

    import requests url = 'https://c.y.qq.com/soso/fcgi-bin/client_search_cp' for x in range(5): headers ...

  3. idea快捷键:查找类中所有方法的快捷键

    查找类中所有方法的快捷键 第一种:ctal+f12,如下图 第二种:alt+7,如下图

  4. smart-adminx项目导入依赖时,点击reinport时没反应且依赖全部报红的解决办法

    依赖报红的解决办法 报红效果如下: 原因分析:下载jar包时,出现大量以.lastUpdated结尾的无效文件. 解决办法:使用bat批处理文件批量删除无效文件 set REPOSITORY_PATH ...

  5. Fastdfs数据迁移方案

    1.     方案背景描述 环境迁移,需要迁移旧环境的fastdfs集群的数据到新环境,由于之前数据迁移仅仅是针对mysql和mongodb,对fastdfs数据的迁移了解甚少,本文档主要是针对fas ...

  6. MySQL 导出 select 结果集

    reference: https://blog.csdn.net/huaishuming/article/details/74762652法一:SELECT * from jc_archives wh ...

  7. Spring IoC总结

    Spring 复习 1.Spring IoC 1.1 基本概念 1.1.1 DIP(Dependency Inversion Principle) 字面意思依赖反转原则,即调用某个类的构造器创建对象时 ...

  8. bootstrap日期范围选择插件daterangepicker详细使用方法

    插件官方网站地址 bootstrap-daterangepicker是个很方便的插件,但是对我这种菜鸟来说,文档不够详细,摆弄了好久才整好.记录下来供以后参考,也希望能帮到有需要的朋友. 目前版本是2 ...

  9. Elasticsearch--Logstash定时同步MySQL数据到Elasticsearch

    新地址体验:http://www.zhouhong.icu/post/139 一.Logstash介绍 Logstash是elastic技术栈中的一个技术.它是一个数据采集引擎,可以从数据库采集数据到 ...

  10. vue关于导航守卫的几种应用场景

    beforeEach 该钩子函数主要用来做权限的管理认证 router.beforeEach((to, from, next) => { if (to.matched.some(record = ...