JFinal нестандартный обзор, на этот раз я серьезно

Java

введение

Я случайно столкнулся с JFinal вчера, когда смотрел на серверный контейнер, Раньше у меня сложилось впечатление, что JFinal — это фреймворк, разработанный китайцами для интеграции корзин семейства Spring.

Я проверил позже, и кажется, что все не так просто.

JFinal много лет выигрывал лучший проект с открытым исходным кодом в OSChina.Это не интеграция корзины семейства Spring, как я понял раньше, а разработка набора WEB + ORM + AOP + Template Engine framework, капитализация классно!

Давайте взглянем на знакомство с официальным складом:

Это введение написано, оно действительно глубоко в моем сердце为您节约更多时间,去陪恋人、家人和朋友 :).

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

Так как я никогда не разбирался в таком прекрасном фреймворке, для ветерана Java это абсолютно невыносимо.

Итак, сегодня я проведу нестандартную оценку фреймворка, чтобы увидеть, может ли он делать то, о чем говорится в лозунге.节约更多的时间, в конце концов хорошо это или нет.

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

Следующая цель — просто создать демонстрацию и выполнить простейшие операции CRUD, чтобы испытать JFinal.

Построить проект

Я с благоговением открыл официальную документацию JFinal.

Так же увидел пример проекта на официальном сайте.Надо спуститься и посмотреть.В этот раз произошло то чего я совсем не ожидал,и я даже попросил меня зарегистрироваться и авторизоваться.О боже мой , это 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», чтобы получить его.

Ваше внимание к скан-коду — самый большой стимул для редактора придерживаться оригинальности :)