Практика с вами для реализации генератора кода
⚠️Эта статья является первой подписанной статьей сообщества Nuggets, перепечатка без разрешения запрещена.
предисловие
Я не знаю, если вы когда-либо упали в сумасшедший CV-код в своей работе, посмотрели на густые класс и не хотели этого делать или потратили много времени на дне. Взяв автор в качестве примера, я часто сталкиваюсь со следующими двумя проблемами:
- Время от времени необходимо создавать новое приложение, требующее различных копипастов (отсутствие настраиваемого каркаса)
- Куча Entity, Bean, Request, Response, DTO, Dao, Service и Business нужно написать под новые требования, а я не хочу этим заниматься.
Во многих случаях некоторые ключевые аннотации даже пропускаются при копировании и вставке кода, например: @Service, что делает невозможным запуск проекта, а затем тратит больше энергии на расследование. Поэтому в этой статье в качестве примера будет использоваться фактический инженерный код для создания настраиваемого высокомасштабируемогоГенератор кода.
Цели проекта
Проект будет запущен для генерации прямого / Sprilboot ключ может быть легко скопирован элемент на основе готового генератора проекта, включая базовые операции базы данных, а также в режиме работы веб-интерфейса.
В то же время он поддерживает настраиваемую реализацию плагинов и обладает высокой масштабируемостью.Следующая является базовой структурой проекта:
Code-Generate
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── mysql
│ │ │ ├── App.java // 程序入口
│ │ │ ├── bean
│ │ │ │ ├── ClassInfo.java // 类实体(对应表维度)
│ │ │ │ ├── ConfigurationInfo.java // 配置中心
│ │ │ │ ├── FieldInfo.java // 字段实体(对应表字段维度)
│ │ │ │ └── GlobleConfig.java // 全局配置
│ │ │ ├── engine
│ │ │ │ ├── AbstractEngine.java // 抽象引擎
│ │ │ │ ├── GeneralEngine.java // 接口引擎
│ │ │ │ └── impl
│ │ │ │ ├── CustomEngineImpl.java // 自定义引擎(拔插式基类)
│ │ │ │ └── DefaultEngine.java // 默认引擎
│ │ │ ├── factory
│ │ │ │ ├── ClassInfoFactory.java // 类工厂(非必要,可融进上述配置中心)
│ │ │ │ └── PropertiesFactory.java // 配置文件工厂(非必要,可融进上述配置中心)
│ │ │ ├── intercept
│ │ │ │ ├── CustomEngine.java
│ │ │ │ └── impl
│ │ │ │ ├── DataMdImpl.java // 自定义引擎案例一(数据库文档)
│ │ │ │ └── LayUiHtmlImpl.java // 自定义引擎案例二(视图界面)
│ │ │ └── util
│ │ │ ├── DataBaseUtil.java // 数据库依赖
│ │ │ ├── DBUtil.java // 数据库操作类
│ │ │ ├── IOTools.java // 工具类
│ │ │ └── StringUtil.java // 工具类
│ │ ├── resources
│ │ │ ├── application.properties // 配置文件
│ │ │ ├── log4j2.xml // 日志配置
│ │ │ └── templates // 模板目录
│ │ └── test
│ └── META-INF
└── MANIFEST.MF // META-INF文件,为了打成Jar包使用(非必须)
кодирование
построить центр конфигурации
Поскольку этот проект включает в себя операционный уровень базы данных и т. д., в дополнение к основным параметрам, таким как целевой каталог, имя проекта, автор, корневой каталог и т. д., ему также требуется конфигурация, связанная с базой данных, и т. д. Файлы конфигурации следующие:
# 数据库IP, 数据库Driver, 编码, 用户名, 密码
ip=127.0.0.1
port=3306
driver=com.mysql.jdbc.Driver
dataBase=school-miao
encoding=UTF-8
loginName=root
passWord=
# 需要构建的表名 为* 默认包含全部, 以;号隔离比如: a;b;c;d;
include=*;
# 项目名
projectName=Demo
# 包名
packageName=com.demo
# 作者
authorName=Kerwin
# 项目输出根目录
rootPath=F:\\code
# 自定义Handle包含项, 现有自定义模块:DataMdImpl,LayUiHtmlImpl, * 号默认包含所有,以;号隔离比如: a;b;c;d;
customHandleInclude=DataMdImpl;LayUiHtmlImpl;
После рассмотрения основного содержимого конфигурации вы можете прочитать файл конфигурации и поместить соответствующую информацию в центр конфигурации.Конкретный код не будет отображаться, и вы можете напрямую просмотреть исходный код, если он вам нужен (ссылка в конце статья).
Классы, задействованные на этом этапе: ConfigurationInfo.java, GlobalConfig.java, PropertiesFactory.
Запись: com.mysql.engine.AbstractEngine#init
Получить информацию о поле таблицы на основе базы данных
Информация о конфигурации целевой базы данных была получена выше.На этом этапе вы можете подключиться к базе данных и получить информацию о таблицах и полях целевой базы данных с помощью общего SQL.
Возьмите следующий SQL в качестве примера, вам нужно только получить соединение с базой данных и информацию о библиотеке, чтобы получить все имена таблиц.
SELECT
table_name
FROM
information_schema.TABLES
WHERE
table_schema = "school-miao-demo" # 库名
AND table_type = "base table";
# 响应
# schools
Точно так же все поля и их типы указанной таблицы данных также можно получить через общий SQL:
SELECT
column_name,
data_type,
column_comment,
numeric_precision,
numeric_scale,
character_maximum_length,
is_nullable nullable
FROM
information_schema.COLUMNS
WHERE
table_name = 'schools' # 表明
AND table_schema = 'school-miao-demo'; # 库名
# 结果为
# column_name data_type
# sname varchar
Получив тип поля таблицы, вы можете сохранить его в центре конфигурации.Ключевым моментом является то, что вам нужно обратить внимание на сопоставление типов данных, таких какvarchar
картаString
,int
картаInteger
И т. д., в то же время поля таблицы преобразуются в методы отображения верблюжьего регистра (фиксированный тип) через класс инструмента обработки строк, например: s_id => sid, на этом этапе вам необходимо обратить внимание на спецификации дизайна полей базы данных.
Создание файлов на основе шаблонов
Самый важный момент в этом проекте заключается в следующем. Если вы хотите реализовать настраиваемый генератор кода, должна быть предпосылка: сама конфигурация. Помните, когда мы впервые изучали JSP много лет назад, вы чувствовали, чтоJSTL
Выражение довольно волшебное, его можноJava
язык иHTML
Язык представляет собой идеальное сочетание, и, хотя мы его больше не используем, этот шаблонный способ мышления и работы именно там, где он должен быть.
В результате исследований было обнаружено, что в технологиях, подобных JSP,FreeMarker
Идеально соответствует нашим ожиданиям, где это:
freemarker.template.Template#process(java.lang.Object, java.io.Writer)
метод, который может помочь нам достичь наполнения контента, указав файлы шаблонов (FTL), объекты Java и целевые файлы.Метод использования аналогиченJSTL
,следующее:
package ${packageName}.entity;
import java.io.Serializable;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* ${classInfo.classComment}
* @author ${authorName} ${.now?string('yyyy-MM-dd')}
*/
@Data
public class ${classInfo.className} implements Serializable {
private static final long serialVersionUID = 1L;
<#if classInfo.fieldList?exists && classInfo.fieldList?size gt 0>
<#list classInfo.fieldList as fieldItem >
/**
* ${fieldItem.columnName} ${fieldItem.fieldComment}
*/
private ${fieldItem.fieldClass} ${fieldItem.fieldName};
</#list>
</#if>
}
в приведенном выше кодеpackageName
,classInfo.classComment
,classInfo.className
И так далее — информация о конфигурации, которую мы получили ранее.
<#list>
Метки могут быть итеративными метками в FreeMarker, нам нужно только оставить измененные части в соответствии с нашими потребностями.
Реализовать подключаемый интерфейс
В соответствии с приведенным выше прогрессом мы уже можем сгенерировать проект, удерживая шаблон (конечно, нам нужно настроить каталог шаблона), но как добиться высокого расширения, подключаемого интерфейса?
Идеи следующие:
- Укажите интерфейс на основе SPI
- Получить класс реализации указанного класса на основе отражения
не знаю что такоеSPI
Друзья могут прочитать эту статью:"Взгляните" на механизм Java SPI.
Поскольку этот проект не хочет, чтобы другие проекты зависели от него, принимается второй метод, заимствующий знаменитыйreflections
Пакет для сканирования класса реализации.
Официальный адрес Maven:Внутри репозитория MV.com/artifact/or…
Основной код выглядит следующим образом:
public final class CustomEngineImpl {
/***
* 扫描全包获取 实现CustomEngine接口的类
*/
private static Set<Class<? extends CustomEngine>> toDos () {
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage(""))
.filterInputsBy(input -> {
assert input != null;
return input.endsWith(".class");
}));
return reflections.getSubTypesOf(CustomEngine.class);
}
public static void handleCustom() {
Set<Class<? extends CustomEngine>> classes = toDos();
for (Class<? extends CustomEngine> aClass : classes) {
// 基于配置项检测是否需要启用自定义实现类
if("*;".equals(GlobleConfig.getGlobleConfig().getCustomHandleInclude()) ||
GlobleConfig.getGlobleConfig().getCustomHandleIncludeMap().containsKey(aClass.getSimpleName())) {
try {
// 基于反射构建对象 - 调用handle方法
CustomEngine engine = aClass.newInstance();
engine.handle(GlobleConfig.getGlobleConfig(), ClassInfoFactory.getClassInfoList());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
Например, мы можем реализовать класс шаблона документа базы данных для достижения идеального эффекта расширения.Шаблон выглядит следующим образом:
# 基础介绍
| 说明 | 内容 |
| :------- | ---- |
| 项目名 | ${config.projectName} |
| 作者 | ${config.authorName} |
| 数据库IP | ${config.ip} |
| 数据库名 | ${config.dataBase} |
<#if classInfos?exists && classInfos?size gt 0>
<#list classInfos as classInfo >
## ${classInfo.tableName}表结构说明
| 代码字段名 | 字段名 | 数据类型(代码) | 数据类型 | 长度 | NullAble | 注释 |
| :--------- | ------ | ---------------- | -------- | ---- | -------------- | ---- |
<#list classInfo.fieldList as fieldItem >
| ${fieldItem.fieldName} | ${fieldItem.columnName} | ${fieldItem.fieldClass} | ${fieldItem.dataType} | ${fieldItem.maxLength} | ${fieldItem.nullAble} | ${fieldItem.fieldComment} |
</#list>
</#list>
</#if>
Показать результаты:
Результаты теста
Сказав так много, давайте посмотрим на эффект напрямую ~
Укажите, чтобы исследовать: как реализация шаблона FreeMarker
Наиболее важным моментом этого проекта является то, что FreeMarker помогает нам реализовать самые сложные этапы файлов генерации контента шаблонов, его функции иJSP
изJSTL
Синтаксис очень похож.Теперь давайте изучим и изучим его лежащий в основе принцип реализации, чтобы увидеть, есть ли что-то стоящее изучения.
От простого к сложному, если вас просят реализовать метод, который обычно поддерживает только замену текстаJSTL
Грамматика, вам сложно? На мой взгляд, это несложно, нам нужно только указать специальную грамматику, запустить наши правила разбора, а затем сопоставить внутренние поля с объектами сущностей, которые мы храним, а затем вывести, например:
// 我是一个测试#{demo.name}
// 按行读取,触发#{}正则匹配时,将 demo.name 替换为 Map/其他数据结构的值即可。
Простое решение, а потом подумать о проблеме, как решить следующие потребности?
<#list classInfo.fieldList as fieldItem>
/**
* ${fieldItem.columnName} ${fieldItem.fieldComment}
*/
private ${fieldItem.fieldClass} ${fieldItem.fieldName};
</#list>
Предположим, Теги ошибки, если они несовместимы (с предыдущими тегами, нет более поздних тегов), что вы думаете?
Это иLeeCode
в алгоритме生成括号
Вопрос немного похож.В этом вопросе левая и правая скобки сгенерированного () должны совпадать одна за другой.Тогда мы обязательно подумаем об использовании этого при выполнении этого вопроса.栈
эту структуру данных. увидеть сноваFreeMarker
Как этого добиться, код выглядит следующим образом:
/**
* freemarker.core.Environment#visit(freemarker.core.TemplateElement)
* "Visit" the template element.
*/
void visit(TemplateElement element) throws IOException, TemplateException {
// 临时数组,存储数据
pushElement(element);
try {
// 构建子元素集
TemplateElement[] templateElementsToVisit = element.accept(this);
// 遍历子元素
if (templateElementsToVisit != null) {
for (TemplateElement el : templateElementsToVisit) {
if (el == null) {
break; // Skip unused trailing buffer capacity
}
// 递归遍历
visit(el);
}
}
} catch (TemplateException te) {
handleTemplateException(te);
} finally {
// 移出数据
popElement();
}
}
Его дизайнерская идея очень проста, оТекст разбивается построчно, а затем рекурсивно проходится построчно, диаграмма DEBUG выглядит следующим образом, следующие узлы элементов имеют в общей сложности 13 строк и начинают перемещаться построчно.
Что касается родителя элемента итерации в тексте, то есть тега , то получен набор всех элементов внутри него, а затем на основе определенного класса обработки:freemarker.core.IteratorBlock#acceptWithResult
Все они грубо и разумно завершены в одно время, и диаграмма DEBUG выглядит следующим образом:
На данный момент обработка итераторов завершена, главное здесь то, чтоКак определить, какой код принадлежит итерации, это похоже на генерацию скобок😎~
Суммировать
Основным ядром этого проекта являются два встроенных запроса полей таблицы через шаблоны mysql и FreeMaker для создания обычного и общего содержимого кода.Технические моменты включают, но не ограничиваются:
- FreeMaker
- собственный XML mybatis, включая добавление, пакетное добавление, удаление, пакетное удаление, запрос на подкачку с несколькими условиями, запрос списка, одиночный запрос, изменение отдельных данных и т. д.
- журнал регистрации
- SpringBoot
- Подключаемый перехватчик (реализован на основе org.reflections), поддерживает сканирование указанных интерфейсов
Исходный код проекта (базовый модуль поддержки, модуль HTML, модуль документа базы данных):Адрес GitHub
Как программисту, разумному ленивому, небо! 😁
Если вы считаете этот контент полезным:
- Конечно, ставьте лайки и поддерживайте~
- Кроме того, вы можете искать и следить за официальной учетной записью "это Кервин», давайте вместе пойдем по дороге технологий~ 😋