DSL
Полное название DSL — динамический скриптовый язык (Dynamic Script Language). Это расширение скриптового языка. В DSL обычные параметры обозначаются с помощью :
и имени параметра, а вложенные параметры — с помощью #
и имени параметра. Специальные символы #[]
используются для обозначения динамических фрагментов. При анализе определяется, является ли фактическое переданное значение параметра пустым (null
) или не существует, чтобы решить, сохранять ли динамический фрагмент. Таким образом, можно избежать ручного объединения сложных скриптов программистами и позволить им освободиться от сложной бизнес-логики. Кроме того, скрипты DSL поддерживают макросы для улучшения обработки динамической логики скриптов. Благодаря мощной динамической обработке, наиболее успешным применением DSL на данный момент является область динамического структурированного языка запросов (DSQL), включая Flink SQL (например, Clink), Spark SQL (например, sparktool) и JDBC (например, sqltool).
На примере Maven-проекта:
<!-- https://mvnrepository.com/artifact/cn.tenmg/dsl -->
<dependency>
<groupId>cn.tenmg</groupId>
<artifactId>dsl</artifactId>
<version>${dsl.version}</version>
</dependency>
Если вы используете OpenJDK, вам также необходимо включить реализацию пакета JavaScript-движка, например nashorn-core:
<!-- https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core -->
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.0</version>
</dependency>
Если используемый JDK не поддерживает использование JavaScript-движков, вы можете использовать движок Beanshell вместо этого:
<!-- https://mvnrepository.com/artifact/org.apache-extras.beanshell/bsh -->
<dependency>
<groupId>org.apache-extras.beanshell</groupId>
<artifactId>bsh</artifactId>
<version>2.0b6</version>
</dependency>
При использовании движка Beanshell вам также потребуется добавить файл конфигурации dsl.properties в каталог classpath (обычно это каталог resources в Maven-проекте), настроив следующее содержимое:
# Включить реализацию макросов на основе Beanshell
macro.eval-engine=cn.tenmg.dsl.eval.BeanshellEngine
DSLUtils.parse
, передав динамический скрипт и параметры для анализа:// Полученный объект NamedScript содержит именованные параметры (такие как «:paramName») и таблицу сопоставления параметров.
NamedScript namedScript = DSLUtils.parse("SELECT\r\n" + " *\r\n" + "FROM STAFF_INFO S\r\n"
+ "WHERE #[if(:curDepartmentId == '01') 1=1 -- Добавить условие тождества, чтобы последующие динамические условия могли быть унифицированы без необходимости удаления «AND» (примечание: здесь это однострочный комментарий)]\r\n"
+ " #[elseif(:curDepartmentId == '02' || :curDepartmentId == '03') S.DEPARTMENT_ID = :curDepartmentId]\r\n"
+ " #[else S.DEPARTMENT_ID = :curDepartmentId AND S.POSITION = :curPosition]\r\n"
+ " /* Комментарии могут быть внутри динамических фрагментов, динамические фрагменты будут сохранены вместе с комментариями, если они будут удалены; \r\n"
+ " Комментарии также могут быть вне динамических фрагментов, комментарии в динамических фрагментах будут полностью сохранены в скрипте. \r\n"
+ " Однострочные комментарии начинаются с префикса, многострочные комментарии начинаются и заканчиваются префиксом и суффиксом соответственно. \r\n"
+ " Для однострочных комментариев, когда используется один символ, символ «#» не может использоваться; когда используются два символа, символы «#[» не могут использоваться. */\r\n"
+ " Для многострочных комментариев первый символ суффикса не может быть символом «]». */\r\n"
+ " #[AND S.STAFF_ID = :staffId]\r\n"
+ " #[AND S.STAFF_NAME LIKE :staffName]", "staffName", "June");
DSLUtils.toScript
, чтобы преобразовать скрипт с именованными параметрами в фактический исполняемый скрипт (и параметры). Существует два встроенных парсера параметров:(1) Используйте парсер параметров JDBC для анализа параметров в заполнители ?
и получения списка параметров:
// Объект NamedScript в сочетании с парсером параметров дополнительно преобразуется для получения фактически исполняемого скрипта. Например, встроенный парсер JDBCParamsParser может анализировать параметры в скрипте в заполнители «?» и получать список параметров.
Script<List<Object>> script = DSLUtils.toScript(namedScript.getScript(), namedScript.getParams(), JDBCParamsParser.getInstance());
String sql = script.getValue();
List<Object> params = script.getParams();
// Затем вы можете выполнить SQL и параметры через JDBC!
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = dataSource.getConnection();
con.setReadOnly(true);
ps = con.prepareStatement(sql);
if (params != null && !params.isEmpty()) {
for (int i = 0, size = params.size();
``` Данный текст представляет собой фрагмент программного кода на языке Java. В запросе присутствуют фрагменты кода, которые невозможно перевести автоматически без потери смысла и структуры исходного текста.
Вот перевод некоторых фрагментов:
«…если в тексте запроса есть код на любом языке программирования, **гиперссылки**, специальные теги форматирования в markdown, html, yaml, json, plantuml и другие, то оставь код и ключи в парах ключ-значение без перевода, переведи только значения ключей…»
В данном тексте присутствуют гиперссылки, но их невозможно отобразить в ответе.
* *i < size; i++) {*
*ps.setObject(i + 1, params.get(i));*
*}*
*rs = ps.executeQuery();*
*«// …»*
*«// 或者»*
*// ps.execute();*
*con.commit();*
*} finally {*
*JDBCUtils.close(rs);*
*JDBCUtils.close(ps);*
*JDBCUtils.close(con);*
*}*
Это фрагмент кода на языке Java, который выполняет итерацию по массиву или списку с использованием цикла for, устанавливает объект с индексом i + 1 равным значению параметра с индексом i, выполняет запрос к базе данных и закрывает ресурсы после выполнения запроса.
*(2) Использование **явного парсера параметров**, разбор для подстановки параметров в исполняемый код:*
// 或者,也可以 напрямую преобразовать в явный код для последующего выполнения. String script = DSLUtils.toScript(namedScript.getScript(), namedScript.getParams(), new PlaintextParamsParser() {
@Override
protected String convert(Object value) {
if (value instanceof Date) {
return parse((Date) value);
} else if (value instanceof Calendar) {
Date date = ((Calendar) value).getTime();
if (date == null) {
return "null";
} else {
return parse(date);
}
} else {
return value.toString();
}
}
private String parse(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
return "'" + sdf.format(date) + "'";
}
}).getValue();
// Выполнить явный код. // …*
Динамические фрагменты
DSL использует специальные символы #[] для обозначения динамических фрагментов, которые вместе с динамическими параметрами образуют динамические фрагменты. Динамические фрагменты могут быть любыми фрагментами скрипта.*
Пример
Например, можно выполнить динамический анализ SQL-скрипта. Предположим, что у нас есть таблица сотрудников STAFF_INFO, структура которой описана следующим оператором создания таблицы:*
CREATE TABLE STAFF_INFO (
STAFF_ID VARCHAR(20) NOT NULL, /*员工编号*/
STAFF_NAME VARCHAR(30) DEFAULT NULL, /*员工姓名*/
DEPARTMENT_ID VARCHAR(10) DEFAULT NULL, /*部门编号*/
POSITION VARCHAR(30) DEFAULT NULL, /*所任职位*/
STATUS VARCHAR(20) DEFAULT 'IN_SERVICE',/*在职状态*/
PRIMARY KEY (`STAFF_ID`)
);*
Обычно нам часто нужно искать информацию о сотрудниках по номеру сотрудника или по имени сотрудника. Для этого нам нужно упорядочить и объединить условия поиска, всего существует 2^2=4 возможных SQL. Если мы используем технику объединения SQL, это будет неэффективно. Если условий поиска больше, объединение SQL станет сложной задачей. Поэтому нам нужна технология, которая поможет нам решить эту проблему, и динамические фрагменты приходят на помощь. С динамическими фрагментами мы можем легко решить эту проблему.*
```SELECT
*
FROM STAFF_INFO S
WHERE 1=1
#[AND S.STAFF_ID = :staffId]
#[AND S.STAFF_NAME LIKE :staffName]*
С помощью приведённого выше SQL с динамическими фрагментами можно автоматически генерировать необходимый для выполнения SQL. Например:*
1. Параметр staffId равен нулю (null), а staffName не равен нулю (не null), тогда фактически выполняемый оператор:*
SELECT * FROM STAFF_INFO S WHERE 1=1 AND S.STAFF_NAME LIKE :staffName*
SELECT
*
FROM STAFF_INFO S
WHERE 1=1
AND S.STAFF_ID = :staffId*
3. Или оба параметра staffId и staffName равны нулю (null), тогда фактически выполняемый оператор:*
SELECT * FROM STAFF_INFO S WHERE 1=1*
SELECT
*
FROM STAFF_INFO S
WHERE 1=1
AND S.STAFF_ID = :staffId
AND S.STAFF_NAME LIKE :staffName* **Код выполняет движок Java, не поддерживает доступ к параметрам через точки и квадратные скобки для доступа к объектам Map. Поэтому в логике суждения можно использовать только простые имена параметров, а не символы доступа к параметрам.**
### Расширение макросов
Можно расширить макрос, реализовав интерфейс `cn.tenmg.dsl.Macro`. Интерфейс имеет следующий исходный код:
public interface Macro { /** * Выполняет макрос и анализирует динамический фрагмент DSL. Если результат возвращает true, то анализ DSL немедленно завершается, и результат текущего анализа макроса динамического фрагмента DSL становится окончательным результатом анализа DSL; в противном случае результат текущего анализа макроса фрагмента DSL будет добавлен к основному результату анализа DSL, и последующий анализ будет продолжен. * @param context контекст DSL * @param attributes таблица атрибутов. Хранится текущим слоем уже выполненных макросов для использования последующими слоями выполнения макросов * @param logic код логики * @param dslf динамический фрагмент DSL * @param params параметры выполнения макроса * @return если результат возвращает true, анализ DSL немедленно прекращается, и результат анализа текущего макроса динамического фрагмента DSL становится конечным результатом анализа DSL; иначе результат анализа текущего фрагмента DSL добавляется к основному результату анализа DSL и последующий анализ продолжается. */ boolean execute(DSLContext context, Map<String, Object> attributes, String logic, StringBuilder dslf, Map<String, Object> params) throws Exception; }
**Описание параметров интерфейса:**
| Параметр | Значение | Описание |
| --- | --- | --- |
| `context` | DSL-контекст | Контекст анализа выполнения DSL, по умолчанию пустой `DefaultDSLContext`, можно передать собственный контекст на этапе анализа. |
| `attributes` | Таблица атрибутов | Хранится предыдущим слоем уже выполненных макросов, используется последующими слоями для выполнения макросов. Например, макросы `if`, `elseif`, `else` должны хранить результаты предыдущего суждения для помощи в последующем суждении. |
| `logic` | Код логики макроса | Часть кода между именем макроса и круглыми скобками, например, логика суждения макросов `if` и `elseif`. |
| `dslf` | Динамический фрагмент DSL | Остальная часть кода, заключённая в макрос, за исключением имени макроса, круглых скобок и кода логики. |
| `params` | Параметры выполнения макроса | Таблица поиска параметров, используемых текущим макросом. |
С версии 1.2.4 поддерживаются два способа внедрения макросов: один использует аннотацию `@Macro` и конфигурацию сканирования пакетов, другой — прямое указание класса реализации макроса в конфигурации. Рекомендуется использовать режим аннотации, так как он более гибкий и позволяет избежать многократного изменения конфигурации. С версии 1.3.0 добавлен способ расширения макросов с использованием механизма загрузки сервисов Java.
#### Режим аннотации
1. Настройка пакета сканирования
В конфигурации настройте `scan.packages`, чтобы указать пакет сканирования, и DSL автоматически просканирует и настроит макросы.
scan.packages=mypackage
2. Написание класса реализации макроса
package mypackage;
import java.util.Map;
import cn.tenmg.dsl.annotion.Macro;
@Macro(name = "MyMacroName") public class MyMacro implements cn.tenmg.dsl.Macro {
@Override
boolean execute(DSLContext context, Map<String, Object> attributes, String logic, StringBuilder dslf,
Map<String, Object> params) throws Exception
// TODO Your logic to process dslf to an actual running script
return false;// Returns false, indicating that the currently processed script is only part of the actual running script
// Or returns true, indicating that the currently processed script ascends as the actual running main script and ignores all previously processed scripts and stops processing subsequent scripts
}
}
#### Режим конфигурации класса
Помимо режима аннотации, можно также указать класс реализации макроса непосредственно в конфигурации, чтобы расширить или переопределить макрос. Например, можно настроить `macro.MyMacroName=mypackage.MyMacro` в конфигурации, чтобы активировать следующий макрос:
package mypackage;
import java.util.Map;
import cn.tenmg.dsl.Macro;
public class MyMacro implements Macro {
@Override
boolean execute(DSLContext context, Map<String, Object> attributes, String logic, StringBuilder dslf,
Map<String, Object> params) throws Exception
// TODO Your logic to process dslf to an actual running script
return false;// Returns false, indicating that the currently processed script is only part of the actual running script
// Or returns true, indicating that the currently processed script ascends as the actual running main script and ignores all previously processed scripts and stops processing subsequent scripts
}
}
#### Режим загрузки сервиса
Кроме того, можно расширить реализацию макроса с помощью механизма загрузки сервисов Java и при этом поддерживать использование аннотаций для настройки имён макросов. Нужно только создать файл META-INF/services/cn.tenmg.dsl.Macro в пути к классам (classpath) и записать имена классов в файле, разделяя несколько классов символами новой строки. Например, структура проекта Maven выглядит следующим образом:
resources └─META-INF └─services └─cn.tenmg.dsl.Macro
Добавьте конфигурацию в файл cn.tenmg.dsl.Macro, как показано ниже:
mypackage.MyMacro
### Механизм выполнения
Механизм выполнения используется для выполнения кода логики суждения макроса. Он имеет встроенную реализацию на основе JavaScript и BeanShell. Для расширения необходимо реализовать интерфейс `cn.tenmg.dsl.EvalEngine`:
public interface EvalEngine {
/**
* Вызывается перед выполнением кода
*/
void open();
/**
* Вставляет параметры в объект кода выполнения
*
* @param params
* параметры
*/
void put(Map<String, Object> params) throws Exception;
/**
* Выполняет код
*
* @param code
* код
* @return результат выполнения кода
* @throws Exception
* если возникает исключение
*/
Object eval(String code) throws Exception;
/**
* Вызывается после выполнения кода
*/
void close();
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )