введение
Я случайно столкнулся с JFinal вчера, когда смотрел на серверный контейнер, Раньше у меня сложилось впечатление, что JFinal — это фреймворк, разработанный китайцами для интеграции корзин семейства Spring.
Я проверил позже, и кажется, что все не так просто.
JFinal много лет выигрывал лучший проект с открытым исходным кодом в OSChina.Это не интеграция корзины семейства Spring, как я понял раньше, а разработка набора WEB + ORM + AOP + Template Engine framework, капитализация классно!
Давайте взглянем на знакомство с официальным складом:
Это введение написано, оно действительно глубоко в моем сердце为您节约更多时间,去陪恋人、家人和朋友 :)
.
Как фермер, который не хочет заканчивать работу пораньше и уходить с работы нормально, не получайте хорошие новости о 996 каждый день.
Так как я никогда не разбирался в таком прекрасном фреймворке, для ветерана Java это абсолютно невыносимо.
Итак, сегодня я проведу нестандартную оценку фреймворка, чтобы увидеть, может ли он делать то, о чем говорится в лозунге.节约更多的时间
, в конце концов хорошо это или нет.
Возможно, это первая статья в отрасли, посвященная оценке фреймворка, так что давайте не будем привлекать внимания: у меня ограниченные возможности, если следующее содержание неверно, пожалуйста, Хайхан.
Следующая цель — просто создать демонстрацию и выполнить простейшие операции CRUD, чтобы испытать JFinal.
Построить проект
Я с благоговением открыл официальную документацию JFinal.
- Адрес документа:jfinal.com/doc
Так же увидел пример проекта на официальном сайте.Надо спуститься и посмотреть.В этот раз произошло то чего я совсем не ожидал,и я даже попросил меня зарегистрироваться и авторизоваться.О боже мой , это 2020 год. Скачивая демо оказалось, что я должен войти в систему, я слепой?
Ладно-ладно, ты босс, последнее слово за тобой, кто заставляет меня жадничать до твоего тела?
Официальная демонстрация сборки проекта - использовать eclipse Ну, вы снова выиграли, я использовал идею, чтобы следовать вашим шагам.
Процесс на самом деле очень прост, то есть создать проект maven, а затем ввести в него зависимости.Основные зависимости следующие:
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal-undertow</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId>
<version>4.9</version>
</dependency>
Я не буду публиковать полный код (в конце концов, он слишком длинный), код будет отправлен в репозиторий кода, и заинтересованные студенты могут посетить репозиторий кода, чтобы получить его.
На самом деле, я привык к процессу создания проектов в SpringBoot, и я не привык строить проекты таким образом.Я исключаю поддержку IDEA для построения проекта SpringBoot и прямой доступstart.spring.io/, просто отметьте и выберите зависимости, которые вам нужны, чтобы напрямую загрузить и импортировать их в IDE.
Но об этом и говорить нечего, ведь за SpringBoot стоит большая команда, а за JFinal вроде бы всего один разработчик, можно сказать, в принципе, гордость китайцев в сфере open source.
Начало проекта
Зависимости проекта сделаны, и первое, что нужно найти способ запустить проект.В JFinal есть глобальный класс конфигурации, и код для запуска проекта также находится здесь.
Этот класс должен наследоватьJFinalConfig
, и для наследования этого класса необходимо реализовать следующие шесть абстрактных методов:
public class DemoConfig extends JFinalConfig {
public void configConstant(Constants me) {}
public void configRoute(Routes me) {}
public void configEngine(Engine me) {}
public void configPlugin(Plugins me) {}
public void configInterceptor(Interceptors me) {}
public void configHandler(Handlers me) {}
}
configConstant
Этот метод в основном используется для настройки некоторых постоянных значений JFinal, таких как: установить прокси aop для использования cglib, установить журнал для использования системы журнала slf4j, формат кодировки по умолчанию — UTF-8 и так далее.
Ниже приведены некоторые конфигурации, указанные в официальных документах, которые я выбрал:
public void configConstant(Constants me) {
// 配置开发模式,true 值为开发模式
me.setDevMode(true);
// 配置 aop 代理使用 cglib,否则将使用 jfinal 默认的动态编译代理方案
me.setToCglibProxyFactory();
// 配置依赖注入
me.setInjectDependency(true);
// 配置依赖注入时,是否对被注入类的超类进行注入
me.setInjectSuperClass(false);
// 配置为 slf4j 日志系统,否则默认将使用 log4j
// 还可以通过 me.setLogFactory(...) 配置为自行扩展的日志系统实现类
me.setToSlf4jLogFactory();
// 设置 Json 转换工厂实现类,更多说明见第 12 章
me.setJsonFactory(new MixedJsonFactory());
// 配置视图类型,默认使用 jfinal enjoy 模板引擎
me.setViewType(ViewType.JFINAL_TEMPLATE);
// 配置 404、500 页面
me.setError404View("/common/404.html");
me.setError500View("/common/500.html");
// 配置 encoding,默认为 UTF8
me.setEncoding("UTF8");
// 配置 json 转换 Date 类型时使用的 data parttern
me.setJsonDatePattern("yyyy-MM-dd HH:mm");
// 配置是否拒绝访问 JSP,是指直接访问 .jsp 文件,与 renderJsp(xxx.jsp) 无关
me.setDenyAccessJsp(true);
// 配置上传文件最大数据量,默认 10M
me.setMaxPostSize(10 * 1024 * 1024);
// 配置 urlPara 参数分隔字符,默认为 "-"
me.setUrlParaSeparator("-");
}
Вот общая информация о конфигурации некоторых проектов.В SpringBoot эта информация о конфигурации обычно записывается наyaml
илиproperty
В файле конфигурации, но я лично считаю, что эта конфигурация не имеет значения, просто немного неудобно.
configRoute
Этот метод предназначен для настройки информации о маршрутизации доступа.Мой пример написан следующим образом:
public void configRoute(Routes me) {
me.add("/user", UserController.class);
}
Увидев это, я думаю о проблеме, каждый раз добавляя новуюController
Было бы глупо приходить сюда для настройки маршрутной информации.
Если это небольшой проект, то нормально, и маршрутной информации много не возвращается, их десяток-десяток, если какой-то средний и крупный проект, то их сотни или тысячи.Controller
, если я настрою его здесь, я могу найти его?Здесь вопросительный знак.
Здесь есть фатальная проблема в практических приложениях: когда версия выходит, студенты, которые делали проекты, знают, что есть как минимум четыре среды: разработка, тестирование, UAT и производство. Функциональная версия кода в каждой среде отличается. Нужно ли мне вручную изменять его перед публикацией? Как этим можно управлять?
configEngine
Это используется для настройки Template Engine, который является шаблоном страницы. Поскольку я просто хочу просто написать два интерфейса Restful, я не буду настраивать его здесь. Ниже приведен официальный пример:
public void configEngine(Engine me) {
me.addSharedFunction("/view/common/layout.html");
me.addSharedFunction("/view/common/paginate.html");
me.addSharedFunction("/view/admin/common/layout.html");
}
configPlugin
Вот плагин, используемый для настройки JFinal, то есть некоторая информация о плагине Мой код выглядит следующим образом:
public void configPlugin(Plugins me) {
DruidPlugin dp = new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password").trim());
me.add(dp);
ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
arp.addMapping("user", User.class);
me.add(arp);
}
Моя конфигурация очень проста: плагин пула соединений с базой данных Druid настраивается спереди, а плагин доступа к базе данных ActiveRecord настраивается сзади.
Что заставляет меня чувствовать себя немного глупо, так это то, что если я хочу увеличить отношение сопоставления доступа к базе данных ActiveRecord, мне нужно вручную добавить сюда код, напримерarp.addMapping("aaa", Aaa.class);
, или вернуться к вышеуказанной проблеме, систему публикации между различными средами необходимо изменить вручную, проект невелик и им можно управлять вручную, если проект большой, он станет кошмаром.
configInterceptor
Этот метод используется для настройки глобальных перехватчиков.Глобальные перехватчики делятся на две категории: уровень управления и бизнес-уровень.Мой пример кода выглядит следующим образом:
public void configInterceptor(Interceptors me) {
me.add(new AuthInterceptor());
me.addGlobalActionInterceptor(new ActionInterceptor());
me.addGlobalServiceInterceptor(new ServiceInterceptor());
}
здесьme.add(...)
иme.addGlobalActionInterceptor(...)
Эти два метода полностью эквивалентны, и оба настроены с перехватчиками, которые перехватывают методы действий во всех контроллерах. иme.addGlobalServiceInterceptor(...)
Настроенный перехватчик будет перехватывать все общедоступные методы бизнес-уровня.
Про перехватчик и говорить нечего, поэтому конфигурация ощущается точно так же, как и в SpringBoot.
configHandler
Этот метод используется для настройки обработчика JFinal, который может принимать все веб-запросы и иметь полный контроль над приложением.
Этот метод является высокоуровневым методом расширения. Я просто хочу написать простую операцию CRUD, которая совершенно не нужна. Вот отрывок из официальной демонстрации:
public void configHandler(Handlers me) {
me.add(new ResourceHandler());
}
конфигурационный файл
Посмотрел официальный файл конфигурации, а конец оказался txt, что заставило меня на первый взгляд усомниться в жизни, почему файл конфигурации должен быть в формате txt, а формат конфигурации внутри точно такой же, как и файл свойств, это показать личность Что ж, это вызвало у меня большие подозрения.
В предыдущем классе конфигурации DemoConfig содержимое файла конфигурации можно получить напрямую через Prop:
static Prop p;
/**
* PropKit.useFirstFound(...) 使用参数中从左到右最先被找到的配置文件
* 从左到右依次去找配置,找到则立即加载并立即返回,后续配置将被忽略
*/
static void loadConfig() {
if (p == null) {
p = PropKit.useFirstFound("demo-config-pro.txt", "demo-config-dev.txt");
}
}
Хотя концепция конфигурации среды представлена в файле конфигурации, она все еще немного грубая.Многое содержимое, которое необходимо настроить, не может быть настроено, и здесь можно настроить только ограниченное содержимое, такое как база данных и служба кэширования.
Конфигурация модели
Честно говоря, я был ошеломлен, когда впервые увидел использование этой части Модели, я не ожидал, что это будет так просто:
public class User extends Model<User> {
}
Все нормально, в нем нечего писать, что полностью переворачивает мое предыдущее познание.Этот фреймворк динамически заходит в базу данных для поиска полей?Это не проблема интеллекта или сообразительности.Если два человека вместе разрабатывают один и тот же проект , я не знаю, какие атрибуты в этой модели, просто глядя на код, я должен смотреть на базу данных вместе, что заставит людей падать.
Позже выяснилось, что я был молод, и код был еще нужен, но мне не пришлось писать его самому.JFinal предоставляет генератор кода.Соответствующий код генерируется автоматически по таблице базы данных, и сгенерированный код не читается.Вы только посмотрите на этот автоматический генератор кода:
public static void main(String[] args) {
// base model 所使用的包名
String baseModelPackageName = "com.geekdigging.demo.model.base";
// base model 文件保存路径
String baseModelOutputDir = PathKit.getWebRootPath() + "/src/main/java/com/geekdigging/demo/model/base";
// model 所使用的包名 (MappingKit 默认使用的包名)
String modelPackageName = "com.geekdigging.demo.model";
// model 文件保存路径 (MappingKit 与 DataDictionary 文件默认保存路径)
String modelOutputDir = baseModelOutputDir + "/..";
// 创建生成器
Generator generator = new Generator(getDataSource(), baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir);
// 配置是否生成备注
generator.setGenerateRemarks(true);
// 设置数据库方言
generator.setDialect(new MysqlDialect());
// 设置是否生成链式 setter 方法
generator.setGenerateChainSetter(false);
// 添加不需要生成的表名
generator.addExcludedTable("adv", "data", "rate", "douban2019");
// 设置是否在 Model 中生成 dao 对象
generator.setGenerateDaoInModel(false);
// 设置是否生成字典文件
generator.setGenerateDataDictionary(false);
// 设置需要被移除的表名前缀用于生成modelName。例如表名 "osc_user",移除前缀 "osc_"后生成的model名为 "User"而非 OscUser
generator.setRemovedTableNamePrefixes("t_");
// 生成
generator.generate();
}
Когда я увидел этот код, у меня похолодело сердце. Фактически была просканирована вся база данных. К счастью, используется MySQL, который является открытым и бесплатным. Если это Oracle, проекту нужна база данных или кластер базы данных. Это слишком много. , деньги.
Конечно, этот код также позволяет исключить имена таблиц, которые не нужно генерировать.addExcludedTable()
Этот метод на самом деле не имеет большого практического значения. В кластере Oracle может одновременно выполняться N нескольких проектов. Выше приведены сотни или тысячи таблиц. Если в небольшом проекте используется только дюжина таблиц,addExcludedTable()
По оценкам, простое копирование имени таблицы в этот метод займет день или два.
CRUD-операции базы данных
JFinal интегрирует CRUD-операции данных в Модель. Я не буду комментировать этот подход. Давайте взглянем на пример класса службы, который я написал:
public class UserService {
private static final User dao = new User().dao();
// 分页查询
public Page<User> userPage() {
return dao.paginate(1, 10, "select *", "from user where age > ?", 18);
}
public User findById(String id) {
System.out.println(">>>>>>>>>>>>>>>>UserService.findById()>>>>>>>>>>>>>>>>>>>>>>>>>");
return dao.findById(id);
}
public void save(User user) {
System.out.println(">>>>>>>>>>>>>>>>UserService.save()>>>>>>>>>>>>>>>>>>>>>>>>>");
user.save();
}
public void update(User user) {
System.out.println(">>>>>>>>>>>>>>>>UserService.update()>>>>>>>>>>>>>>>>>>>>>>>>>");
user.update();
}
public void deleteById(String id) {
System.out.println(">>>>>>>>>>>>>>>>UserService.deleteById()>>>>>>>>>>>>>>>>>>>>>>>>>");
dao.deleteById(id);
}
}
Запрос подкачки здесь меня немного сбивает с толку.Почему предложение SQL должно быть разделено на две половины, я всегда чувствую, что вторая половинаfrom user where age > ?
Это Hibernate HQL. Есть ли какой-то секрет между ними?
Другие распространенные CRUD-операции вполне нормальные, без каких-либо слотов.
Controller
Начнем с кода, поговорим о коде:
public class UserController extends Controller {
@Inject
UserService service;
public void findById() {
renderJson(service.findById("1"));
}
public void save() {
User user = new User();
user.set("id", "2");
user.set("create_date", new Date());
user.set("name", "小红");
user.set("age", 24);
service.save(user);
renderNull();
}
public void update() {
User user = new User();
user.set("id", "2");
user.set("create_date", new Date());
user.set("name", "小红");
user.set("age", 19);
service.update(user);
renderNull();
}
public void deleteById() {
service.deleteById(getPara("id"));
renderNull();
}
}
Использование первого сервиса@Inject
Для впрыска об этом и говорить нечего, а тот, что в Спринге@Autowaire
Такой же.
Тип возвращаемого значения всех фактических методов в этом классе:void
Пустой тип, возвращаемый контент зависит отrender()
Для контроля можно вернуться в json или вернуться к просмотру страницы, или просто немного неудобно, ничего страшного в этом нет.
Но потом эта проблема сделала меня немного более квадратным, и это не было похоже на проблему, а стало недостатком.Есть всего два способа получить параметры:
одинgetPara()
Ряд методов, этот метод может получить только данные, представленные формой, которая в основном похожа на форму в Spring.request.getParameter()
.
ДругойgetModel / getBean
, во-первых, эти два метода принимают параметры, отправленные через форму, а во-вторых, ее необходимо преобразовать в класс Model.
Я просто хочу знать одну вещь, если тип запроса не является отправкой формы, аapplication/json
, как принимать параметры, я несколько раз листал документ, но не нашел то, что хотелrequest
объект.
Может быть, я просто не нашел, зрелый фреймворк не должен поддерживать это общееapplication/json
метод подачи данных, это невозможно.
Также,getModel / getBean
Этот метод должен быть непосредственно преобразован в класс модели, что иногда не очень хорошо.Если текущий формат входных параметров этого интерфейса относительно сложен, по-прежнему сложно построить такую модель, особенно иногда нужно только получить Небольшой объем данных анализируется и предварительно обрабатывается, и нет необходимости анализировать все данные запроса.
резюме
Глядя на это через простую операцию CRUD, JFinal выполнил то, что должен иметь фреймворк WEB + ORM в целом, но некоторые части не так хороши.Конечно, это по сравнению со SpringBoot.
Если он используется для создания каких-то мелких вещей, его все равно стоит попробовать.Если он предназначен для приложений корпоративного уровня, он кажется немного растянутым.
Однако этот проект вышел относительно рано, прошло уже 8 лет с 2012. Если сравнивать с фреймворком SpringMCV+Spring+ORM того года, думаю, если выберу, то однозначно выберу JFinal.
Если сравнивать с нынешним SpringBoot, думаю, я все же склоняюсь к выбору SpringBoot, один из-за знакомства, а другой из-за того, что в JFinal много места.Для удобства разработчиков много кода инкапсулировано.Это Я не могу сказать, что это плохо. Это определенно хорошо для новичков. Просто посмотрите документацию. В принципе, вы можете начать работать через полдня или день. Но для некоторых старых водителей это заставит людей чувствовать себя связанными. Этого тоже нельзя.
Я отправил свой собственный пример кода и официальную демонстрацию в репозиторий кода. Студенты, которым это нужно, могут ответить на «JFinal», чтобы получить его.