Анализ архитектуры Activiti и подробное объяснение исходного кода

Архитектура

введение

Механизм рабочего процесса используется для решения таких проблем, как утверждение процессов и организация процессов, и обеспечивает эффективную поддержку масштабируемости. В настоящее время поле рабочего процесса также имеет относительно общую стандартную спецификацию, то есть BPMN2.0. Основными движками с открытым исходным кодом, поддерживающими эту спецификацию, являются: Activiti, flowable, Jbpm4 и т. д. В этой статье основное внимание уделяется анализу и разбору архитектуры Activiti, и в то же время проводится полное чтение соответствующих кодов запуска процессов и атомарных операций.

Читатели этой статьи должны иметь определенное представление об Activiti и быть в состоянии изначально использовать Activiti для разработки потока процессов.

Если у вас есть сомнения по поводу содержания статьи, присоединяйтесь к техническому обмену-186233599, и время от времени автор будет отвечать на актуальные вопросы.

1. Анализ дизайна Activiti — архитектура и модель предметной области

1.1 Архитектура

Activiti использует многоуровневую архитектуру для создания упаковки «снизу вверх». Схема архитектуры выглядит следующим образом

Примерно включает:

  • Базовый уровень интерфейса, определяемый интерфейсом PVM. PVM будет подробно рассмотрен в последующих главах.
  • Реализован базовый слой, а интерфейс, основанный на идее PVM, определяет некоторые из ключевых сущностей, включающих ActivityImpl (реализованы узлы абстрактного класса), реализацию FlowElementBehavior (узел операции инструкции абстрактного класса), ExecutionImpl (класс сущности выполнения процесса )
  • На командном уровне Activiti напрямую определяет общий стиль как командный режим в режиме кодирования. То есть бизнес-логика инкапсулируется в класс реализации командного интерфейса один за другим. Таким образом, при добавлении бизнес-функции вам нужно только добавить реализацию команды. Здесь следует особо отметить, что сама команда должна выполняться в контексте команды, то есть в объекте класса CommandContext.
  • Уровень перехвата команд принимает модель цепочки ответственности и создает условия для выполнения команды через уровень перехватчика модели цепочки ответственности. Например, открытие транзакции, создание контекста CommandContext, ведение журнала и т. д.
  • Уровень бизнес-интерфейса ориентирован на бизнес и предоставляет различные интерфейсы. Эта часть интерфейса больше ориентирована не на разработчиков фреймворка, а на пользователей фреймворка.
  • Слой развертывания, строго говоря, не является полноценной многоуровневой системой, как упоминалось выше. Но для того, чтобы выделить важность, вынесите его отдельно. Предпосылкой операции процесса является определение процесса. И синтаксический анализ определения процесса — это то, с чего все начинается. Уровень развертывания опирается на объекты POJO, которые анализируются с языка предметной области на Java. Эта часть будет подробно рассмотрена позже.
  • Движок процессов, общая запись для всех интерфейсов. Уровень бизнес-интерфейса и уровень развертывания, упомянутые выше, могут быть получены из класса механизма процесса. Поэтому интерфейс Process Engine здесь фактически аналогичен режиму фасада, который используется только как точка входа.

1.1.1 Командный режим

Activit использует командный режим в целом, чтобы отделить кодовые функции. Большая часть движка процесса связана с требованиями клиента, а внешняя реализация реализована в виде конкретных классов реализации команд.

Чтобы завершить этот режим кодирования, необходимо обратить внимание на несколько ключевых классов.

  • CommandКомандный интерфейс, все конкретные команды должны реализовать этот класс, и последнее дело — выполнить метод execute этого класса.
  • CommandContextКонтекст команды, обеспечивающий поддержку контекста для выполнения определенных команд. Контекст генерируется, полагаясь на перехватчик контекста в перехватчике команд.org.activiti.engine.impl.interceptor.CommandContextInterceptorгенерировать. Перехватчик определит, следует ли повторно использовать текущий контекст или сгенерировать новый контекст.

Большая часть функций внутри движка выполняется с помощью отдельных команд.

1.1.2 Модель цепочки ответственности

Командный режим Activiti также должен быть дополнен соответствующей цепочкой ответственности. В частности, в Activiti есть цепочка перехватчиков команд, которая состоит из нескольких больших блоков реализаций перехватчиков, а именно:

Среди них есть 2 важных перехватчика по умолчанию:

  • Основная обязанность перехватчика транзакций состоит в том, чтобы последующие команды выполнялись в транзакционной среде.
  • Перехватчик CommandContext, основной обязанностью которого является создание объектов CommandContext при необходимости и закрытие контекста после использования.
1.1.2.1 Перехватчик транзакций

Наличие перехватчика транзакций зависит отorg.activiti.engine.impl.cfg.ProcessEngineConfigurationImplПодкласс методаcreateTransactionInterceptorреализация. при самостоятельном использованииorg.activiti.engine.impl.cfg.StandaloneProcessEngineConfigurationМетод возвращает пустое значение. То есть перехватчик транзакций не предоставляется. В этот момент выполнение команды не может предоставить среду транзакций через перехватчик транзакций.

1.1.2.2 Перехватчик командного контекста

Класс реализации: org.activiti.engine.impl.interceptor.CommandContextInterceptor.

Функция этого перехватчика очень важна и, можно сказать, является одним из основных направлений работы Activiti. Его функция заключается в проверке текущего контекста перед выполнением следующего перехватчика и, если объекта CommandContext нет, создать его; после выполнения последующего перехватчика закрыть объект CommandContext. CommandContext содержит все объекты данных, задействованные в этой операции.

1.1.3 Анализ определения процесса

Activiti следует спецификации BPMN2.0, поэтому платформа незаменима для класса синтаксического анализа файла определения (форма XML) спецификации BPMN2.0. Activiti использует модель извлечения STAX для синтаксического анализа XML. Здесь мы не будем анализировать внутреннюю взаимосвязь конкретных классов синтаксического анализа, а концептуально объясним концептуальное наслоение синтаксического анализа Activiti.

первый в классеorg.activiti.bpmn.converter.BpmnXMLConverterВыполнить синтаксический анализ XML, проанализированный какorg.activiti.bpmn.model

Классы POJO в пакете, соответствующие соответствующим определениям элементов XML. На данный момент эти классы POJO являются просто Java-представлением XML-файла.

В проходном классеorg.activiti.engine.impl.bpmn.parser.BpmnParserАгрегировать различные классы синтаксического анализа и дополнительно анализировать классы POJO, проанализированные на предыдущих шагах, на те, которые можно использовать в инфраструктуре.org.activiti.engine.impl.pvm.processКлассы ниже package. Типичным представителем является класс ActivityImpl.

Отношение между этими тремя просто выражается графически как

1.2 Модель предметной области

Activiti использует модель гиперемии в предметной области как собственную реализацию. Большая часть бизнес-логики напрямую связана сorg.activiti.engine.impl.persistence.entity.ExecutionEntityсередина. Так как Activiti использует продукты O/R Mapping, такие как MyBatis, вместо Hibernate в качестве слоя сохраняемости, Activiti также имеет свой собственный уникальный способ конкретных операций сохранения.

1.2.1 DataSet представлен

Механизм сохраняемости Activiti простоПредставлено в наборе данных. Централизованные коммиты имеют дополнительный эффект: автоматические коммиты. Другими словами, у находящихся в памяти сущностей, которые обновляют свойства, но явно не выполняют действие обновления, также будет сохраняться их последнее состояние в базе данных после завершения жизненного цикла вызова. Давайте взглянем на подробное объяснение этого централизованного механизма отправки.

Все объекты, созданные во время выполнения в Activiti, должны реализовать интерфейс.org.activiti.engine.impl.interceptor.Session, который определяется следующим образом

public interface Session
{
  void flush();
  void close();
}

И объект сеанса находится по интерфейсуorg.activiti.engine.impl.interceptor.SessionFactoryМетод генерации, как определено ниже

public interface SessionFactory {
  Class<?> getSessionType();
  Session openSession();
}

Механизм обработки содержит различные реализации SessionFactory, и пользователи также могут зарегистрировать свои собственные реализации SessionFactory, если пользователь хочет, чтобы пользовательские объекты обрабатывались централизованным механизмом отправки.

В CommandContext имеется хранилище Map, в котором хранятся все объекты Session, вновь созданные в жизненном цикле CommandContext. Когда команда завершает выполнение, вызывается метод закрытия последнего контекста команды CommandContext. При выполнении метода CommandContext.close() его внутреннее выполнение будет выполняться по порядку.flushSessions,closeSessionsметод. Как видно из названия, внутри первого метода выполняется метод flush всех объектов Session, а внутри второго метода выполняется метод close всех объектов Session.

Совершенно особенным является наличие реализации сеанса внутри механизма процесса. этоorg.activiti.engine.impl.db.DbSqlSessionвыполнить. Если есть такие операции, как обновление, удаление, вставка и т. д., то операцию нужно реализовать через DbSqlSession, и фактически реализация будет кэшировать эти операции внутри. Только при выполнении метода сброса он будет фактически отправлен в базу данных для выполнения. Именно из-за этого все операции с данными в конечном счете обращаются к базе данных только тогда, когда CommandContext выполняет метод close.

Теоретически, модель скопления - это постоянство, сделанная на уровне совокупного корня. Однако, поскольку Activiti не использовал структуру отображения O / R, он завершил модуль с аналогичными функциями.

1.2.2 PersistentObject

Чтобы понять механизм вставки данных в рабочий процесс, сначала посмотрите на интерфейс класса сущностей.org.activiti.engine.impl.db.PersistentObject,следующее

public interface PersistentObject {
  String getId();
  void setId(String id);
  Object getPersistentState();
}

Все таблицы базы данных Activiti представляют собой однострочные первичные ключи, и каждый класс сущностей должен реализовать этот интерфейс. При сохранении класса сущности DbSqlSession вызовет метод getId, чтобы определить, есть ли идентификатор, если нет, используйте генератор идентификаторов (у этого генератора есть несколько различных реализаций в соответствии со стратегией, не показанной здесь), чтобы сгенерировать идентификатор и установить его. .

и методgetPersistentStateиспользуется для возврата объекта постоянного состояния. Использование метода описано в следующем разделе.

1.2.3 DbSqlSession

Наличие внутреннего сеанса три важных свойства, следующие

//该属性存储着所有使用insert方法放入的对象
protected Map<Class<? extends PersistentObject>, List<PersistentObject>> insertedObjects = new HashMap<Class<? extends PersistentObject>, List<PersistentObject>>();
//该Map结构内存储所有通过该DbSqlSession查询出来的结果,以及update方法放入的对象
protected Map<Class<?>, Map<String, CachedObject>> cachedObjects = new HashMap<Class<?>, Map<String,CachedObject>>();
//该属性内存储着所有将要执行的删除操作
protected List<DeleteOperation> deleteOperations = new ArrayList<DeleteOperation>();

Удаление и добавление относительно легко понять, то есть кэшировать такие операции и отправлять их в базу данных за один раз.В этом месте отражена централизованная подача данных, упомянутая выше. И cachedObjects немного отличается. Чтобы разобрать эту структуру карты, сначала посмотрите на классorg.activiti.engine.impl.db.DbSqlSession.CachedObjectСтруктурные свойства , как показано ниже

public static class CachedObject {
    protected PersistentObject persistentObject;
    protected Object persistentObjectState;
}
public CachedObject(PersistentObject persistentObject, boolean storeState) {
      this.persistentObject = persistentObject;
      if (storeState) {
        this.persistentObjectState = persistentObject.getPersistentState();
      }
    }

Из метода построения можно понять, что при новом создании объекта параметр storeState используется для определения необходимости сохранения постоянного состояния в это время.

Для этой карты есть 2 источника данных

  • Все результирующие объекты запросов, выполненных через объект DbSqlSession, будут генерировать соответствующий объект CachedObject, а параметр storeState имеет значение true.
  • При выполнении метода класса обновления объекта DbSqlSession параметры будут заключены в объект CachedObject, а параметр storeState имеет значение false.

Когда метод выполнения DBSQLSESSE FLUSH, он в основном выполнен из представленных данных

  1. Вставить элементы из списка insertObjects в базу данных
  2. Перемещайтесь по элементам в списке deleteOperations
  3. Выполните метод getUpdatedObjects, чтобы обновить объекты сущностей.

Логика метода getUpdatedObjects заключается в обходе всех объектов CachedObject, и те из них, которые соответствуют следующим условиям, помещаются в коллекцию сущностей для обновления.

  1. Метод Entity getPersistentState не равен нулю
  2. Метод объекта getPersistentState возвращает объект и persistenceObjectState, хранящиеся в CachedObject, для выполнения одинакового суждения, и результат ложный.

Он автоматически обновит в базу данных можно узнать из вышеперечисленного, если личный класс жизненного цикла DBSQLSession запрашивается, и его содержимое данных был изменен, затем обновить DBSQLSESSE.

2. Анализ проекта Activiti — дерево выполнения PVM

2.1 Основная концепция

Любой фреймворк разрабатывается и дорабатывается из основной концепции. Основной концепцией Activiti является Process Virtual Machine (PVM). PVM пытается предоставить набор API, которые описывают различные возможности с точки зрения рабочего процесса через сам API. Без специальной реализации сам PVM может лучше адаптироваться к различным доменным языкам рабочего процесса, и сама Activiti также является реализацией PVM.

2.1.1 Описание PVM периода определения процесса

Давайте сначала посмотрим на само определение процесса. В рабочем процессе определение процесса может быть представлено графически в виде набора узлов и соединений. Например, следующий рисунок

Даже без каких-либо знаний можно примерно понять, что эта диаграмма выражает замысел процесса и порядок выполнения. Нет ограничений на выражение определения процесса, оно может быть выражено в форме графики, доменного языка или традиционного XML (например, Activiti использует XML в схеме BPMN2.0). В частности, в настоящее время существует стандартизированная спецификация BPMN 2.0.

PVM описывает определение процесса как набор элементов процесса. Затем элементы процесса подразделяются на два подкласса: узлы процесса и соединения.

  • Узел процесса — это абстрактное описание выражения действия. Сами узлы могут быть вложенными, то есть узлы могут иметь дочерние узлы.
  • Проводное представление — это отношение перехода между различными узлами. Ссылка может иметь только один исходный узел и один целевой узел. Сам узел может иметь любое количество входящих и исходящих соединений.

Эту взаимосвязь также хорошо видно с точки зрения диаграммы классов.Узел процесса PvmActivity и соединение PvmTransition являются элементами процесса PvmProcessElement.

Из диаграммы классов видно, что PvmActivity наследуется от PvmScope. Это отношение наследования указывает на то, что сам узел процесса имеет свою собственную область (PvmScope), и сам узел также может быть областью действия других узлов, что также соответствует принципу, согласно которому узел может иметь дочерние узлы. Что касается самой области действия, то она будет подробно объяснена позже, но здесь она не указана.

2.1.2 Описание PVM процессов, запущенных на

Через узлы и соединения процессов PVM завершает выражение определений процессов. Определение процесса — это статическое выражение процесса, а выполнение процесса — это выражение времени выполнения, запускаемое в соответствии с определением процесса.Каждое выполнение процесса имеет свой уникальный жизненный цикл. Для выполнения процесса необходимы следующие элементы:

  1. Конкретное действие выполнения узла процесса.
  2. В каком узле процесса в данный момент находится выполнение процесса.
  3. Выполнение процесса — это то, как он проходит от одного узла к другому.
  4. Как выполнение процесса выполняет действие выполнения, определенное узлом процесса

Для элемента 1 Activiti предоставляет интерфейсorg.activiti.engine.impl.pvm.delegate.ActivityBehavior.该接口内部仅有一个execute方法。该接口的实现即为不同PvmActivity节点提供了具体动作。 ActivityBehavior有丰富的不同实现,对应了流程中丰富的不同功能的节点。每一个PvmActivity对象都会持有一个ActivityBehavior对象。

Для элемента 2 Activiti предоставляет интерфейсorg.activiti.engine.impl.pvm.PvmExecution. В интерфейсе есть методPvmActivity getActivity(). Используется для возврата узла процесса, в котором находится текущее выполнение процесса.

Для элемента 3 Activiti предоставляет интерфейсorg.activiti.engine.impl.pvm.runtime.InterpretableExecution. Существует много интерфейсных методов, здесь два наиболее важных метода для выборки и выполнения процесса расширены следующим образом.

public interface InterpretableExecution extends ActivityExecution, ExecutionListenerExecution, PvmProcessInstance {
  void take(PvmTransition transition);
  void take(PvmTransition transition, boolean fireActivityCompletedEvent);
 

Выполните метод take с объектом соединения в качестве входного параметра, что заставит процесс выполнить маршрут, определенный соединением. Логика реализации должна позволять выполнению процесса определять местонахождение активного узла в источнике соединения и достигать активного узла в месте назначения соединения через объект соединения.

Для элемента 4, по сути, он тоже определяется интерфейсомorg.activiti.engine.impl.pvm.runtime.AtomicOperationзавершить. Через вызывающий класс этого интерфейса разработчику этой ситуации необходимо получить информацию об активном узле, где выполняется текущий процесс.ActivityBehaviorобъект, выполнить егоexecuteметод для выполнения действий узла. Комбинируя элементы 3 и 4, можно увидеть, чтоAtomicOperationИнтерфейс используется для выполнения одной инструкции в работе процесса, например, перемещения в соответствии с подключением, выполняя инструкцию узла и т. Д. Преимущество разрушения его в единую инструкцию состоит в том, что это легко кодировать и понимать. Это также соответствует атомному намерению в именах интерфейса.

2.1.3 Обзор PVM

Из приведенного выше объяснения периода определения и периода работы PVM видно, что вся концептуальная система не сложна. Задействовано не так много классов. Именно потому, что PVM абстрагирует и определяет интерфейс только самой базовой части рабочего процесса, это делает реализацию PVM многообещающей.

Однако именно из-за простоты определения эта PVM на самом деле нуждается во множестве дополнительных функций, когда она преобразуется в реальную реализацию, чтобы действительно выполнить требования фреймворка.

2.2 ActivitiImpl и объем

После завершения синтаксического анализа все узлы в определении процесса анализируются как объекты ActivityImpl. Сам объект ActivityImpl может содержать подписки на события (согласно спецификации BPMN2.0 в настоящее время существует три типа подписок на события: синхронизация, сообщение и сигнал). Поскольку сам ActivityImpl может быть вложенным и содержать подписки, вводится понятие области действия (Scope).

ActivityImpl определяется как ActivityImpl с заданной областью действия в следующих двух случаях.

  1. ActivityImpl представляет собой вариабельную область, то это обладает объем. Переменная область может быть понята, поскольку определение содержания узла является переменным. Такие как определение процесса, подпрограмм, его внутреннее содержание является переменным. Согласно определению BPMN, вариабельная область включает в себя: определение процесса, подпроцесс, несколько экземпляров и вызовов деятельности.
  2. ActivityImpl определяет контекст для получения событий. Например: ActivityImpl с граничными событиями, ActivityImpl с подпроцессами событий, шлюз, управляемый событиями, и захват промежуточных событий ActivityImpl.

Область действия — очень важное понятие, в случае 1 область определяет жизненный цикл сложных узлов, а в случае 2 область определяет область захвата событий.

2.3 ExecutionEntity

Значение ExecutionEntity — это экземпляр выполнения после запуска определения процесса, представляющий состояние выполнения процесса. В дизайне Activiti,Подписки на события, переменные процесса и т. д. связаны с конкретным объектом ExecutionEntity.. Он сам по себе имеет несколько важных свойств:

  • isScope: когда это свойство имеет значение true, это означает, что исполняемый экземпляр выполняет узел ActivityImpl с заданной областью действия или выполняет определение процесса. Проще говоря, это означает, что экземпляр выполняет действие с заданной областью.
  • isConcurrent: когда это свойство имеет значение true, это означает, что узлы, принадлежащие к той же области, что и активный узел, который выполняется исполняемым экземпляром, могут одновременно выполняться другими исполняемыми экземплярами (например, 2 параллельные задачи за параллельным шлюзом).
  • isActive: когда это свойство имеет значение true, это означает, что исполняемый экземпляр выполняет простой ActivityImpl (ActivityImpl, который не содержит другого ActivityImpl)
  • isEventScope: когда это свойство имеет значение true, это означает, что экземпляр выполнения является экземпляром выполнения, созданным хранилищем переменных для посткомпенсации. Поскольку все переменные в процессе выполнения должны быть привязаны к ExecutionEntity, компенсация заключается в том, что требуется моментальный снимок исходных переменных. Чтобы выполнить это требование, создайте ExecutionEntity, предназначенный для этого.
  • ActivityId: идентификатор ActivityImpl, который выполняется ExecutionEntity. Выполнение означает несколько вещей: войти в узел, выполнить действие узла, покинуть узел. Если ожидается завершение подпроцесса, это свойство имеет значение null.

Объяснение ExecutionEntity выше все еще абстрактно. Если посмотреть на это интуитивно, то можно подумать, что ExecutionEntity — это воплощение определенного жизненного цикла, и его внутренние свойства меняются при разных ситуациях. Как показано ниже:

По мере запуска процесса появляетсяExecutionEntityобъект. ДолженExecutionEntityЖизненный цикл такой же, как и весь процесс, иisScopeиisConcurrentФиксируется при создании и не меняется. иisActiveиactivityIdОн будет меняться по ходу процесса.

ExecutionEntityОн используется для отражения хода процесса.ExecutionEntityНедостаточно для поддержки всех функций BPMN. Поэтому Activiti реализован через древовидную структуруExecutionEntityСтруктура, отражающая ход процесса. в начале созданияExecutionEntityОбъекты будут продолжать разделяться и объединяться по ходу процесса.ExecutionEntityДеревья тоже постоянно подращивают и подрезают. Есть 4 основные ситуации, с которыми придется столкнуться при продвижении процесса.

  1. отдельный незадействованный узел
  2. отдельный узел области видимости
  3. одновременные узлы без области
  4. Параллельные узлы области

2.3.1 Отдельные узлы без области действия

Эту ситуацию можно показать на следующем рисунке

В составе потока forward встречается один узел non-scope,ExecutionEntityОн всегда активен, но по ходу процесса его точка activityId будет продолжать меняться. Как показано на рисунке выше, он будет иметь четыре разных значения: начальный узел, работа члена группы, одобрение лидером и конечный узел.

На самом деле все эти узлы находятся в области действия , иExecutionEntityОн представляет эту область, поэтому его свойство isScope имеет значение true.

2.3.2 Отдельные узлы области видимости

Если во время продвижения процесса встречается отдельный узел области, текущий исполняемый объектExecutionEntityДочернее выполнение с ограниченной областью (ExecutionEntity с isScope true) должно быть создано. Весь процесс изменения можно показать на следующем рисунке

При подготовке к входу в узел ,ExecutionEntity1Заморозить и создать дочернее исполнениеExecutionEntity2.ExecutionEntity2Свойство isScope также верно.

ExecutionEntity1isScope имеет значение true, поскольку исполнительный экземпляр отвечает за подписку на события, определенную всем процессом.ExecutionEntity2isScope имеет значение true, поскольку исполнительный экземпляр отвечает за подписку на события узла .

Как упоминалось ранее, подписки на события связаны с конкретным экземпляром выполнения. Когда узел будет завершен, то есть когда область, в которой находится подписка на событие, будет уничтожена, соответствующая подписка на событие также будет удалена. В это время доп.ExecutionEntityЭто очень удобно, просто удалитеExecutionEntity, кстати, удалите соответствующую подписку на событие, вот удалитеExecutionEntity2.

УдалитьExecutionEntity2, и активируйте родительское выполнениеExecutionEntity1. По мере продвижения процесса,ExecutionEntity1Замените указанный идентификатор активности.

2.3.3 Параллельные узлы без области действия

Если во время продвижения процесса в узле имеется несколько исходящих соединений, при необходимости может быть создано несколько одновременных подвыполнений, и каждое подвыполнение соответствует соединению. Как показано ниже

Когда узлы процесса A и B активированы,ExecutionEntity1Будет 2 одновременных подвыполненияExecutionEntity2иExecutionEntity3. Эти два подвыполненияisConcurrentОба свойства верны, потому что узлы A и B выполняются одновременно в одной и той же области (определение процесса).

Когда выполнение узлов A и B завершено, параллельное дочернее выполнение удаляется, родительское выполнение повторно активируется, а последующие узлы продолжают работу.

2.3.4 Параллельные узлы области действия

Когда параллельный узел встречается во время продвижения процесса, и этот узел является узлом области, ситуация будет следующей.

Когда процесс выполняется на узле P1, узел P1 имеет несколько исходящих линий. Создайте 2 подвыполнения в соответствии с количеством исходящих строк. В это время два подвыполнения выполняются одновременно, а P1 используется в качестве исходного узла исходящей линии. Следовательно, идентификаторы действий двух подвыполнений равны P1. .

При запуске к узлам A и B, поскольку оба узла являются узлами области действия, будут созданы еще два подвыполнения. В настоящее времяExecutionEntity3иExecutionEntity3заморозить.ExecutionEntity4иExecutionEntity5Выполняемые узлы не имеют параллельных операций в соответствующих областях, поэтому их свойство isScope имеет значение true, а свойство isConcurrent — значение false. Дерево выполнения, сформированное этими 5 экземплярами выполнения, выглядит следующим образом.

Когда узлы A и B завершены, сначала удаляются их соответствующие области, поэтомуExecutionEntity4иExecutionEntity5был впервые удален,ExecutionEntity3иExecutionEntity4активация. Затем сойдитесь на узле P2, так чтоExecutionEntity3иExecutionEntity4Удалить,ExecutionEntity1активируется и продолжает выполнение остальных узлов.

3. Анализ кода — запуск процесса

3.1 Описание процесса

Запуск процесса зависит от класса команды:org.activiti.engine.impl.cmd.StartProcessInstanceCmd.

Общий поток команды выглядит следующим образом

Запрос администратора развертывания имеет мало общего со средой выполнения, поэтому пока игнорируйте его. Выделены два процесса:

  1. Сущность определения процесса Создать экземпляр процесса
  2. Начало выполнения экземпляра процесса

3.1.1 Сущность определения процесса для создания экземпляра процесса

Процесс выглядит следующим образом

вУкажите начальный скользящий узел для создания экземпляраСам расширенный процесс выглядит следующим образом

Инициализация ExecutionEntityНужно сказать отдельно, процесс выглядит следующим образом

В конце логики, создающей поток, находится зацикленный поток. Цель этого цикла — создать правильное дерево ExecutionImpl (далее — дерево выполнения). По сути, этот метод заключается в создании экземпляра процесса и размещении текущего работающего узла процесса в указанном узле. И правильное выполнение рабочего процесса зависит от правильного разбиения и интеграции дерева выполнения. Следовательно, необходимо создать восходящий экземпляр дерева выполнения для указанного узла. Создается впечатление, что процесс автоматически выполняется на текущем действующем узле (дерево выполнения похоже, история выполнения узла не похожа, и на самом деле узла истории нет).

И если указанный начальный узел является начальным узлом определения процесса, цикл не имеет смысла.

3.1.2 Запуск экземпляра процесса

Содержимое запуска экземпляра процесса заключается в выполнении атомарных операций:org.activiti.engine.impl.pvm.runtime.AtomicOperationProcessStart. Атомарные операции описываются отдельно.

3.2 Дополнительные добавки

3.2.1 родительское свойство ActivityImpl

org.activiti.engine.impl.pvm.process.ActivityImplВ классе есть свойство parent, типorg.activiti.engine.impl.pvm.process.ScopeImpl. Во время синтаксического анализа это свойство является узлом области действия текущего узла. Согласно определению узла области, значение этого атрибута имеет два возможных типа:org.activiti.engine.impl.pvm.process.ActivityImpl, другойorg.activiti.engine.impl.pvm.process.ProcessDefinitionImpl.

Второй случай означает, что узел является узлом, непосредственно принадлежащим определению процесса.

В-четвертых, анализ кода — атомарная операция

4.1 Описание

Атомарные операции — это интерфейсorg.activiti.engine.impl.pvm.runtime.AtomicOperation. Как видно из названия, функция этого интерфейса заключается в выполнении одношаговой операции в экземпляре процесса. Ниже приводится пошаговое описание

4.2 AbstractEventAtomicOperation

Этот абстрактный класс является базовым для многих классов реализации. Его код выглядит следующим образом

public abstract class AbstractEventAtomicOperation implements AtomicOperation {
 
  public boolean isAsync(InterpretableExecution execution) {
    return false;
  }
 
  public void execute(InterpretableExecution execution) {
      //获取当前执行对象的作用域对象。具体由子类提供。
    ScopeImpl scope = getScope(execution);
      //从作用域对象中获取指定事件的监听器。事件名称由子类提供。
    List<ExecutionListener> exectionListeners = scope.getExecutionListeners(getEventName());
    int executionListenerIndex = execution.getExecutionListenerIndex();
 
    if (exectionListeners.size()>executionListenerIndex) {
      execution.setEventName(getEventName());
      execution.setEventSource(scope);
      ExecutionListener listener = exectionListeners.get(executionListenerIndex);
      try {
        listener.notify(execution);
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new PvmException("couldn't execute event listener : "+e.getMessage(), e);
      }
      execution.setExecutionListenerIndex(executionListenerIndex+1);
      execution.performOperation(this);
 
    } else {
      execution.setExecutionListenerIndex(0);
      execution.setEventName(null);
      execution.setEventSource(null);
 
      eventNotificationsCompleted(execution);
    }
  }
 
  protected abstract ScopeImpl getScope(InterpretableExecution execution);
  protected abstract String getEventName();
  protected abstract void eventNotificationsCompleted(InterpretableExecution execution);
}

В общем, логика всего абстрактного класса состоит в том, чтобы получить объект области текущего исполняемого экземпляра (в частности, предоставленный подклассом) и выполнить прослушиватель определенного события (имя события предоставляется подклассом).

После выполнения всех слушателей выполняется определенная логика подкласса.

4.3 AtomicOperationProcessStart

Это действие используется для запуска процесса. Но никаких реальных действий по запуску не выполняется. Просто установите активный узел текущего экземпляра выполнения наorg.activiti.engine.impl.pvm.runtime.StartingExecutionАктивные узлы хранятся в . затем выполните атомарную операциюorg.activiti.engine.impl.pvm.runtime.AtomicOperationProcessStartInitial.

По сути, он просто выполняет заданное действие.

public class AtomicOperationProcessStart extends AbstractEventAtomicOperation {
 
  @Override
  protected ScopeImpl getScope(InterpretableExecution execution) {
    return execution.getProcessDefinition();
  }
 
  @Override
  protected String getEventName() {
    return org.activiti.engine.impl.pvm.PvmEvent.EVENTNAME_START;
  }
 
  @Override
  protected void eventNotificationsCompleted(InterpretableExecution execution) {
      if (Context.getProcessEngineConfiguration() != null && Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
        Map<String, Object> variablesMap = null;
        try {
          variablesMap = execution.getVariables();
        } catch (Throwable t) {
          // In some rare cases getting the execution variables can fail (JPA entity load failure for example)
          // We ignore the exception here, because it's only meant to include variables in the initialized event.
        }
        Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
                ActivitiEventBuilder.createEntityWithVariablesEvent(ActivitiEventType.ENTITY_INITIALIZED,
                    execution, variablesMap, false));
      Context.getProcessEngineConfiguration().getEventDispatcher()
              .dispatchEvent(ActivitiEventBuilder.createProcessStartedEvent(execution, variablesMap, false));
    }
 
    ProcessDefinitionImpl processDefinition = execution.getProcessDefinition();
    StartingExecution startingExecution = execution.getStartingExecution();
    List<ActivityImpl> initialActivityStack = processDefinition.getInitialActivityStack(startingExecution.getInitial());
    execution.setActivity(initialActivityStack.get(0));
    execution.performOperation(PROCESS_START_INITIAL);
  }
}

4.4 AtomicOperationProcessStartInitial

код показывает, как показано ниже

public class AtomicOperationProcessStartInitial extends AbstractEventAtomicOperation {
 
  @Override
  protected ScopeImpl getScope(InterpretableExecution execution) {
    return (ScopeImpl) execution.getActivity();
  }
 
  @Override
  protected String getEventName() {
    return org.activiti.engine.impl.pvm.PvmEvent.EVENTNAME_START;
  }
 
  @Override
  protected void eventNotificationsCompleted(InterpretableExecution execution) {
    ActivityImpl activity = (ActivityImpl) execution.getActivity();
    ProcessDefinitionImpl processDefinition = execution.getProcessDefinition();
    StartingExecution startingExecution = execution.getStartingExecution();
     //从开始节点开始的,该判断均为真。
    if (activity==startingExecution.getInitial()) {
      execution.disposeStartingExecution();
      execution.performOperation(ACTIVITY_EXECUTE);
    } else {
      List<ActivityImpl> initialActivityStack = processDefinition.getInitialActivityStack(startingExecution.getInitial());
      int index = initialActivityStack.indexOf(activity);
      activity = initialActivityStack.get(index+1);
 
      InterpretableExecution executionToUse = null;
      if (activity.isScope()) {
        executionToUse = (InterpretableExecution) execution.getExecutions().get(0);
      } else {
        executionToUse = execution;
      }
      executionToUse.setActivity(activity);
      executionToUse.performOperation(PROCESS_START_INITIAL);
    }
  }
}

4.5 AtomicOperationTransitionNotifyListenerEnd

Цель этой атомарной операции состоит только в том, чтобы выполнитьendпрослушиватель событий. После выполнения слушателя выполняется следующая атомарная операция.AtomicOperationTransitionDestroyScope

4.6 AtomicOperationTransitionNotifyListenerStart

Цель этой атомарной операции — запустить прослушиватель событий запуска на узле. После завершения выполнения будет оцениваться, может ли текущий узел исполняемого экземпляра быть выполнен. Решение основано на узле и узле соединительной линии

4.3 AtomicOperationActivityExecute

Функция этой атомарной операции фактически состоит в том, чтобы вывести текущий активный узел экземпляра выполнения и выполнить определение поведения активного узла. Поведение определяется через интерфейсыorg.activiti.engine.impl.pvm.delegate.ActivityBehaviorопределение. Различные поведения узлов выполняются разными подклассами

public class AtomicOperationActivityExecute implements AtomicOperation {
 
  private static Logger log = LoggerFactory.getLogger(AtomicOperationActivityExecute.class);
 
  public boolean isAsync(InterpretableExecution execution) {
    return false;
  }
 
  public void execute(InterpretableExecution execution) {
    ActivityImpl activity = (ActivityImpl) execution.getActivity();
 
    ActivityBehavior activityBehavior = activity.getActivityBehavior();
    if (activityBehavior==null) {
      throw new PvmException("no behavior specified in "+activity);
    }
 
    log.debug("{} executes {}: {}", execution, activity, activityBehavior.getClass().getName());
 
    try {
        if(Context.getProcessEngineConfiguration() != null && Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
          Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
                  ActivitiEventBuilder.createActivityEvent(ActivitiEventType.ACTIVITY_STARTED,
                          execution.getActivity().getId(),
                          (String) execution.getActivity().getProperty("name"),
                          execution.getId(),
                          execution.getProcessInstanceId(),
                          execution.getProcessDefinitionId(),
                          (String) activity.getProperties().get("type"),
                          activity.getActivityBehavior().getClass().getCanonicalName()));
      }
 
      activityBehavior.execute(execution);
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      LogMDC.putMDCExecution(execution);
      throw new PvmException("couldn't execute activity <"+activity.getProperty("type")+" id=\""+activity.getId()+"\" ...>: "+e.getMessage(), e);
    }
  }
}

4.4 AtomicOperationTransitionDestroyScope

public class AtomicOperationTransitionDestroyScope implements AtomicOperation {
 
  private static Logger log = LoggerFactory.getLogger(AtomicOperationTransitionDestroyScope.class);
 
  public boolean isAsync(InterpretableExecution execution) {
    return false;
  }
 
  @SuppressWarnings("unchecked")
  public void execute(InterpretableExecution execution) {
    InterpretableExecution propagatingExecution = null;
 
    ActivityImpl activity = (ActivityImpl) execution.getActivity();
    /**
    * 如果当前的活动节点具备作用域。这就意味着最初的时候,有一个处于激活状态的执行实例在执行该节点,以下称初始执行实例。
    * 从这样的节点退出要考虑几种情况:
    * 一、单独的作用域节点。此时的执行树情况是:非激活的非作用域执行实例-激活的作用域执行实例(初始执行实例)。此时要离开作用域节点,首先是销毁激活的作用域执行实例(初始执行实例),激活初始执行实例的父实例,使用该父实例执行后续的出线动作。
    * 二、并行的作用域节点。此时的执行树情况是:非激活的非作用域并发执行实例-非激活的
    */
    if (activity.isScope()) {
 
      InterpretableExecution parentScopeInstance = null;
      // if this is a concurrent execution crossing a scope boundary
      if (execution.isConcurrent() && !execution.isScope()) {
        // first remove the execution from the current root
        InterpretableExecution concurrentRoot = (InterpretableExecution) execution.getParent();
        parentScopeInstance = (InterpretableExecution) execution.getParent().getParent();
 
        log.debug("moving concurrent {} one scope up under {}", execution, parentScopeInstance);
        List<InterpretableExecution> parentScopeInstanceExecutions = (List<InterpretableExecution>) parentScopeInstance.getExecutions();
        List<InterpretableExecution> concurrentRootExecutions = (List<InterpretableExecution>) concurrentRoot.getExecutions();
        // if the parent scope had only one single scope child
        if (parentScopeInstanceExecutions.size()==1) {
          // it now becomes a concurrent execution
          parentScopeInstanceExecutions.get(0).setConcurrent(true);
        }
 
        concurrentRootExecutions.remove(execution);
        parentScopeInstanceExecutions.add(execution);
        execution.setParent(parentScopeInstance);
        execution.setActivity(activity);
        propagatingExecution = execution;
 
        // if there is only a single concurrent execution left
        // in the concurrent root, auto-prune it.  meaning, the
        // last concurrent child execution data should be cloned into
        // the concurrent root. 
        if (concurrentRootExecutions.size()==1) {
          InterpretableExecution lastConcurrent = concurrentRootExecutions.get(0);
          if (lastConcurrent.isScope()) {
            lastConcurrent.setConcurrent(false);
 
          } else {
            log.debug("merging last concurrent {} into concurrent root {}", lastConcurrent, concurrentRoot);
 
            // We can't just merge the data of the lastConcurrent into the concurrentRoot.
            // This is because the concurrent root might be in a takeAll-loop.  So the
            // concurrent execution is the one that will be receiving the take
            concurrentRoot.setActivity((ActivityImpl) lastConcurrent.getActivity());
            concurrentRoot.setActive(lastConcurrent.isActive());
            lastConcurrent.setReplacedBy(concurrentRoot);
            lastConcurrent.remove();
          }
        }
 
      } else if (execution.isConcurrent() && execution.isScope()) {
        /**
        * 根据算法,这种情况不会出现。源代码中,这部分也属于todo的内容。
        */
      }
        else {
        /**
        * 这个条件是执行实例的scope属性为真。此时销毁当前的执行实例,使用其父执行实例继续后面的流程
        */
        propagatingExecution = (InterpretableExecution) execution.getParent();
        propagatingExecution.setActivity((ActivityImpl) execution.getActivity());
        propagatingExecution.setTransition(execution.getTransition());
        propagatingExecution.setActive(true);
        log.debug("destroy scope: scoped {} continues as parent scope {}", execution, propagatingExecution);
        execution.destroy();
        //删除与该执行实例相关的一切,包括:定时工作,各种任务,事件订阅,用户流程关系,最后删除自身。
        execution.remove();
      }
    } else {
      //如果离开的是一个非作用域节点,则仍然使用当前的执行实例作为下一个节点的执行实例
      propagatingExecution = execution;
    }
 
    // if there is another scope element that is ended
    ScopeImpl nextOuterScopeElement = activity.getParent();
    TransitionImpl transition = propagatingExecution.getTransition();
    ActivityImpl destination = transition.getDestination();
    /**
    * 考虑当前的节点可能是子流程或者活动调用中的节点。那么就需要离开当前的作用域范围,回到更上层的作用域下。因此需要判断目的地是否和源头节点处于同一个作用域。如果不是同一个作用域,则不断向上回溯
    */
    if (transitionLeavesNextOuterScope(nextOuterScopeElement, destination)) {
      propagatingExecution.setActivity((ActivityImpl) nextOuterScopeElement);
      propagatingExecution.performOperation(TRANSITION_NOTIFY_LISTENER_END);
    } else {
      propagatingExecution.performOperation(TRANSITION_NOTIFY_LISTENER_TAKE);
    }
  }
 
  public boolean transitionLeavesNextOuterScope(ScopeImpl nextScopeElement, ActivityImpl destination) {
    return !nextScopeElement.contains(destination);
  }
}

4.5 AtomicOperationTransitionNotifyListenerTake

Цель этой атомарной операции — запустить прослушиватель в сети. После завершения выполнения целевой активный узел готов к выполнению. Здесь также есть выбор целевых узлов. Он не выполняет напрямую целевой активный узел на ссылке. Вместо этого, начиная с целевого активного узла, выберите целевой активный узел или его родительский узел, который принадлежит к той же области, что и текущий активный узел исполняемого экземпляра.

public class AtomicOperationTransitionNotifyListenerTake implements AtomicOperation {
 
  private static Logger log = LoggerFactory.getLogger(AtomicOperationTransitionNotifyListenerTake.class);
 
  public boolean isAsync(InterpretableExecution execution) {
    return false;
  }
 
  public void execute(InterpretableExecution execution) {
    TransitionImpl transition = execution.getTransition();
 
    List<ExecutionListener> executionListeners = transition.getExecutionListeners();
    int executionListenerIndex = execution.getExecutionListenerIndex();
    /**
    * 整个if的功能就是不断判断监听器是否被执行完毕。都执行完毕后走入到else的部分。
    */
    if (executionListeners.size()>executionListenerIndex) {
      execution.setEventName(org.activiti.engine.impl.pvm.PvmEvent.EVENTNAME_TAKE);
      execution.setEventSource(transition);
      ExecutionListener listener = executionListeners.get(executionListenerIndex);
      try {
        listener.notify(execution);
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new PvmException("couldn't execute event listener : "+e.getMessage(), e);
      }
      execution.setExecutionListenerIndex(executionListenerIndex+1);
      execution.performOperation(this);
 
    } else {
        if (log.isDebugEnabled()) {
            log.debug("{} takes transition {}", execution, transition);
        }
      execution.setExecutionListenerIndex(0);
      execution.setEventName(null);
      execution.setEventSource(null);
 
      ActivityImpl activity = (ActivityImpl) execution.getActivity();
      ActivityImpl nextScope = findNextScope(activity.getParent(), transition.getDestination());
      execution.setActivity(nextScope);
 
      // Firing event that transition is being taken       
      if(Context.getProcessEngineConfiguration() != null && Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
          Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
                ActivitiEventBuilder.createSequenceFlowTakenEvent(ActivitiEventType.SEQUENCEFLOW_TAKEN, transition.getId(),
                        activity.getId(), (String) activity.getProperties().get("name") ,(String) activity.getProperties().get("type"), activity.getActivityBehavior().getClass().getCanonicalName(),
                        nextScope.getId(), (String) nextScope.getProperties().get("name"), (String) nextScope.getProperties().get("type"), nextScope.getActivityBehavior().getClass().getCanonicalName()));
      }
 
      execution.performOperation(TRANSITION_CREATE_SCOPE);
    }
  }
 
  /** finds the next scope to enter.  the most outer scope is found first */
  public static ActivityImpl findNextScope(ScopeImpl outerScopeElement, ActivityImpl destination) {
    ActivityImpl nextScope = destination;
    while( (nextScope.getParent() instanceof ActivityImpl)
           && (nextScope.getParent() != outerScopeElement)
         ) {
      nextScope = (ActivityImpl) nextScope.getParent();
    }
    return nextScope;
  }
}

4.6 AtomicOperationTransitionCreateScope

Эта атомарная операция должна подтвердить, имеет ли входящий узел область действия. Замораживает текущий экземпляр выполнения, если он имеет область действия. И создается новый экземпляр выполнения для выполнения узла области; если у него нет области действия, он не действует.

После подтверждения выполните атомарную операциюAtomicOperationTransitionNotifyListenerStart

public class AtomicOperationTransitionCreateScope implements AtomicOperation {
 
  private static Logger log = LoggerFactory.getLogger(AtomicOperationTransitionCreateScope.class);
 
  public boolean isAsync(InterpretableExecution execution) {
    ActivityImpl activity = (ActivityImpl) execution.getActivity();
    return activity.isAsync();
  }
 
  public void execute(InterpretableExecution execution) {
    InterpretableExecution propagatingExecution = null;
    ActivityImpl activity = (ActivityImpl) execution.getActivity();
    if (activity.isScope()) {
      //为作用域活动创建一个新的执行实例,是该原子操作的主要目的
      propagatingExecution = (InterpretableExecution) execution.createExecution();
      propagatingExecution.setActivity(activity);
      propagatingExecution.setTransition(execution.getTransition());
      execution.setTransition(null);
      execution.setActivity(null);
      execution.setActive(false);
      log.debug("create scope: parent {} continues as execution {}", execution, propagatingExecution);
      //这里是另外一个重点。在一个流程实例初始化的时候,会对当前流程所处的作用域对象(可能是流程定义或者是作用域活动进行处理,具体表现是为该作用域对象上的定时事件,消息事件,信号事件执行注册动作。分别是放入定时调度器,在数据库新增事件订阅)
      propagatingExecution.initialize();
 
    } else {
      propagatingExecution = execution;
    }
 
    propagatingExecution.performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_START);
  }
}