Pull to refresh

Fuga Framework — Маленький веб фреймворк для Java

Reading time 5 min
Views 14K
В этой статье я бы хотел рассказать о своем фреймворке, который я нескромно назвал Fuga Framework





Лирическое вступление


Когда я учился на втором курсе, меня пригласили разработать веб-приложение для одной лаборатории кафедры теоретической и экспериментальной физики. Само приложение было довольно простым, но имело несколько необычных возможностей. Первым делом надо было выбрать на каком языке и с помощью каких технологий начать разработку этого самого приложения. Не долго думая, я решил все сделать на PHP без использования какого-либо фреймворка. Конечно, я рассматривал все известные мне варианты, но приложение тогда казалось простым и мне не хотелось сильно заморачиваться. Через неделю я уже закончил предварительную версию приложения.

Результат работал. Но через некоторое время у меня появилось чувство неудовлетворенности и обеспокоенности, что в любой момент все может сломаться. И не зря. Когда приложение было размещено на хостинге, накопилось очень много багов. Начиная исправлять эти нелепые ошибки, я понял, что надо было все-таки воспользоваться каким-нибудь фреймворком.

В тот момент у нас начался курс «Программирование на Java», где для получения экзамена нужно было в конце семестра представить любой проект сделанный на этом языке. И у меня появилась идея сделать Java-микрофреймворк. Поскольку мне все еще нужно было переделывать корявое приложение для лаборатории, я весь семестр делал микрофреймворк именно для этого приложения. При создании этого фреймворка, я вдохновлялся популярным Play Framework’ом. В итоге у меня получилось рабочее веб-приложение для лаборатории, которое мало того было написано на Java, но оказалось гораздо стабильнее и имело возможность легко расширяться. Моему «программистскому» счастью не было предела. Вдохновившись написанием фреймворков, я его отделил от самого приложения, сделал рефакторинг и начал расширять всякими возможностями. Этот фреймворк я удачно использовал в одном учебном и еще в двух личных проектах, а также я получил несколько хороших отзывов от некоторых опытных разработчиков.

Начало


Fuga Framework — встраиваемый веб-фреймворк, требующий в зависимостях только Netty и log4j. Это позволяет использовать фреймворк не только для создания классических веб-приложении, но и встраивать его в другие приложения для простого создания веб-интерфейса. На данный момент Fuga Framework включает в себя HTTP сервер, роутер и шаблонизатор.

Любое приложение использующее Fuga Framework должно иметь как минимум три файла:
  • Главный класс
  • Класс-контроллер
  • План маршрутизации

В главном классе мы настраиваем приложение и запускаем веб-сервер:

package com.example;

import com.showvars.fugaframework.FugaApp;

public class HelloWorldApp extends FugaApp {

    @Override
    public void prepare() {
        getRouter().loadFromResources("/routes/helloworld.routesmap");
    }

    public static void main(String[] args) throws Exception {
        new HelloWorldApp().start();
    }
}

Здесь мы производим лишь настройку маршрутизатора с помощью специального файла. Вот его пример:

use com.example.controllers

GET $/ HelloWorldController.index()

В первой строчке мы сделали импорт пакета, что в дальнейшем позволит не писать полный путь с именем пакета к классам-контроллерам. В следующей строчке мы указываем маршрутизатору, что любой запрос типа GET, удовлетворяющий регулярному выражению /, нужно направить на метод index() в классе HelloWorldController. Если вы сталкивались с Play Framework, то можете заметить схожесть этого файла маршрутизации с подобным файлом в Play Framework. Но на самом деле, в Fuga Framework маршрутизатор можно настроить гораздо гибче, используя дополнительные конструкции, о которых я расскажу позже.

Итак, в файле плана маршрутизации мы направили запрос в метод index(), находящийся в классе HelloWorldController. Давайте создадим его:

package com.example.controllers;

import com.showvars.fugaframework.foundation.*

public class HelloWorldController extends Controller {

    public static Response index(Context ctx) throws Exception {
        return ok("Привет мир!");
    }
}

Любой метод, который вызывает маршрутизатор, должен быть статический и обязательно принимать первым аргументом объект типа Context. Этот объект хранит в себе ссылки на текущий запрос, включая куки и сессии, а также ссылку на инстанс сервера. Из класса Controller мы только наследуем для удобства статические методы, такие как ok(), proceed(), notFound(), redirect() и т.п.

Готово! Теперь можно запустить приложение и открыть браузер: http://localhost:8080/

Настройка


Все настройки веб-сервера, шаблонизатора и всего остального проводится методом set() объекта Configuration. Этот объект можно получить с помощью геттера в методе prepare(), либо из объекта Context в контроллере.

Маршрутизатор


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

В регулярных выражениях можно использовать capture groups, а их значения передавать в контроллер:

GET $/(.*) com.example.ExampleController.index(1)

В контроллер можно подавать константы, а также указывать тип значения. В этом случае они будут приведены автоматически:

GET $/(\d+) com.example.ExampleController.index(1:int, "3.14":double, "some string")

Первые два параметра маршрута не обязательны и могут быть написаны в любом порядке:

com.example.ExampleController.index()
GET com.example.ExampleController.index()
GET $/hello com.example.ExampleController.index()
$/hello GET com.example.ExampleController.index()

Один маршрут может направить запрос на несколько контроллеров последовательно:

GET $/(\d+) {
    HelloWorldController.check(1:int)
    HelloWorldController.index()
}

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

Вложенные маршруты:

$/page/(\d*) {
    GET  $/page/(\d+) HelloWorldController.get(1:int)
    POST $/page/(\d+) HelloWorldController.post(1:int)
    GET {
        HelloWorldController.check(0)
        HelloWorldController.index()
    }
}

Можно использовать предопределенные методы для быстрой отдачи статики пользователю:

GET $/(.*) asset(1:String)
GET $/ view("index.html")

Шаблонизатор


В Fuga Framework шаблонизатор основан на JavaScript движке Mozilla Rhino, который встроен по умолчанию в JRE 7 и выше.

Для вывода используется тег {# <выражение> #}, куда вставляется выражение на языке JavaScript. А для вставки простого кода аналогично используется тег {% <код> %}. Также есть возможность расширения других шаблонов конструкциями вида:

@extend base.html

{% block content %}
<h2>Привет мир!</h2>
{% endblock %}

Чтобы вывести шаблон пользователю, нужно в контроллере использовать метод view() из класса Controller:

return ok(view("index.html"));

либо непосредственно в плане маршрутизации написать view() вместо контроллера:

GET $/ view("index.html")

По умолчанию шаблонизатор компилирует и кэширует шаблон один раз при первом его вызове. Но изменив параметр fuga.templates.alwaysrecompile можно добиться того, что каждый раз шаблон будет скомпилирован заново. Это позволит быстро править шаблон без перезапуска сервера.

При написании шаблонов в нашем распоряжении не только язык JavaScript. Поскольку движок Mozilla Rhino позволяет получить доступ ко всем Java классам, то мы получаем всю стандартную библиотеку, а также объекты классов Context и TemplateApi. Первый идентичен тому, что подается на вход контроллеру, а второй является классом, имеющий набор полезных функций, таких как asset(), escape() и т.п. Пример:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html" />
        <link href="{# api.asset('css/common.css') #}" rel="stylesheet" type="text/css" />
    </head>
    {% var session = context.getSession(); %}
    <body>
        <div id="content">
           <h3>Добро пожаловать, {# session.getString('user') #}</h3>
           {% block content %}{% endblock %} 
        </div>
        <script src="{# api.asset('js/common.js') #}"></script>
        {% block js %}{% endblock %}
    </body>
</html>

Заключение


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

GitHub: https://github.com/IntCode/Fuga-Framework
Tags:
Hubs:
+8
Comments 14
Comments Comments 14

Articles