Небольшое вступление
Spark — это просто чудесный микрофреймворк для создания веб-приложений на джаве без особых усилий. Spark стремится к простоте и обеспечивает только минимальный набор функций. Тем не менее он предоставляет все необходимое для создания веб-приложения, которые поместятся в несколько строк кода. С синтаксисом, вдохновленным Sinatra, код выглядит очень чистым.
Давайте начнем со вездесущого хеллоуворлда.
Создаём новый проект и импортируем Spark. Лично я использую maven для управления зависимостями.
Добавляем в pom.xml.
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.6.0</version>
</dependency>
Теперь приступим непосредственно к «хеллоу ворлду».
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
get("/hello", (req, res) -> "Hello, World!");
}
}
Вот и всё. Всё настолько просто!
Теперь spark прослушивает GET запросы на /hello. Всякий раз, когда мы переходим на localhost:4567/hello, вызывается метод handle(). Внутри него мы возвращаем объект, который должен быть отправлен клиенту (в этом случае «Hello World»).
Лично мое мнение, что данный код настолько лаконичный, что даже не требует пояснений.
Стоп-стоп. Что насчет запуска/остановки сервера?
- Остановка — надо всего лишь вызвать метод stop().
- Запуск — а вот здесь все интересно. Сервер автоматически запускается, когда вы делаете что-то, что требует запуска сервера (я знаю что звучит действительно странно). Но можно запустить и вручную, вызвав метод init().
Поддержка шаблонизаторов
Хотел бы затронуть такую тему как шаблонизаторы. Спарк имеет большую нативную поддержку оных. А именно:
- Velocity
- Freemarker
- Mustache
- Handlebars
- Jade
- Thymeleaf
- Pebble
- Water
- jTwig
- Jinjava
- Jetbrick
Для всех них есть «обёртки» от спарка. Для примера давайте рассмотрим мой любимый Freemarker.
Для начала давайте импортируем доп. зависимость.
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-template-freemarker</artifactId>
<version>2.5.5</version>
</dependency>
Создадим шаблон «hello.ftl» в директории src/main/resources.
<html>
<head>
</head>
<body>
<h1>Hello, ${name}!</h1>
</body>
</html>
Теперь надо сконфигурировать freemarker дабы он искал шаблоны в ресурсах. Это всего лишь 4 строчки кода.
FreeMarkerEngine freeMarkerEngine = new FreeMarkerEngine();
Configuration freeMarkerConfiguration = new Configuration();
freeMarkerConfiguration.setTemplateLoader(new ClassTemplateLoader(Main.class, "/"));
freeMarkerEngine.setConfiguration(freeMarkerConfiguration);
Вот и всё, можно спокойно использовать фримаркер.
get("/", (request, response) -> {
Map<String, Object> model = new HashMap<>();
model.put("name", "Freemarker");
return freeMarkerEngine.render(new ModelAndView(model, "hello.ftl"));
});
Почти полноценный проект
Давайте напишем, какой-то лёгкий, но достаточный что бы показать хотя бы малую часть возможностей спарка? Мне из того что можно реально быстро написать, пришел в голову только сокращатель ссылок. Что ж, начнем?
Надо добавить еще одну зависимость Google Guava. В итоге у нас получается 3 зависимости.
<dependencies>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-template-freemarker</artifactId>
<version>2.5.5</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
Нам Guava нужна, потому что в ней реализована хэш-функция Murmur3. Весь плюс в том что она достаточно сильна устойчива к коллизиям. Так же что бы не тратить лишнее время, мы будем все данные хранить только в переменных, без всяких бд и т.д.
Сделаем костяк программы.
staticFileLocation("/static");
get("/shortener", (request, response) -> {
});
get("/:url", (request, response) -> {
});
:url это параметр который можно достать через метод params(). К примеру:
get("/hello/:name", (request, response) -> {
return "Hello: " + request.params(":name");
});
Что-то я отвлекся, продолжим.
Создадим коллекцию(Map) для хранения, настроим шаблонизатор(как я говорил freemarker мой любимый, так что будет именно он) и укажем спарку где у нас будут храниться статистические файлы. staticFiles.location("/static") укажет ему что файлы будут лежать в src/main/resources/static. К примеру файл /static/css/style.css будет доступен по адресу http://{host}:{port}/css/style.css. Если же вы хотите хранить файлы предположим в /var/www/public_html, тогда используем метод staticFiles.externalLocation("/var/www/public_html").
ConcurrentHashMap<String, String> urls = new ConcurrentHashMap<String, String>();
FreeMarkerEngine freeMarkerEngine = new FreeMarkerEngine();
Configuration freemarkerConfiguration = new Configuration();
freemarkerConfiguration.setTemplateLoader(new ClassTemplateLoader(Main.class, "/templates/"));
freeMarkerEngine.setConfiguration(freemarkerConfiguration);
staticFileLocation("/static");
Настроим перенаправление.
get("/:url", (request, response) -> {
if(urls.containsKey(request.url()))
response.redirect(urls.get(request.url()));
response.redirect("/");
return null;
});
Теперь когда пользователь переходит по короткой ссылке, если ссылка есть в коллекции, мы перемещаем его на нужный сайт, если нету, то на главную.
Давайте поработаем с фронтендом, а то мы совсем про него забыли. Начнем с главной.
Здесь мы создадим простую минималистическую форму. И еще запихнем шрифтик Proxima Nova. Заигрался конечно… ну да ладно.
<!DOCTYPE html>
<html lang="en" style="height:100%;">
<head>
<meta charset='utf-8'>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="main">
<form action="/shortener" method="GET">
<input type="text" id="input" name="url" autocomplete="off" autofocus size="44" maxlength="512" />
</form>
</div>
</body>
</html>
@font-face {
font-family: Proxima Nova;
src: url(pn.otf);
}
* {
font-family: Proxima Nova;
background: #2a2826;
color: #9C9C9C;
margin: 0;
padding: 0;
}
body, html {
height: 100%;
}
#main {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 1.5em;
}
#input {
border: none;
outline:none;
font-size: 1.5em;
}
В итоге все это добро выглядит так.
Теперь страница на которую будет выводится ссылка «shortener.ftl».
<!DOCTYPE html>
<html lang="en" style="height:100%;">
<head>
<meta charset='utf-8'>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="main" style="text-align: center">
${url}
</div>
</body>
</html>
Итоговая структура проекта.
Что ж, дописываем последние строки. В итоге Main класс, выглядит так.
import com.google.common.hash.Hashing;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import spark.ModelAndView;
import spark.Request;
import spark.Response;
import spark.template.freemarker.FreeMarkerEngine;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
ConcurrentHashMap<String, String> urls = new ConcurrentHashMap<String, String>();
FreeMarkerEngine freeMarkerEngine = new FreeMarkerEngine();
Configuration freemarkerConfiguration = new Configuration();
freemarkerConfiguration.setTemplateLoader(new ClassTemplateLoader(Main.class, "/templates/"));
freeMarkerEngine.setConfiguration(freemarkerConfiguration);
staticFileLocation("/static");
get("/shortener", (request, response) -> {
String shortURL = "http://localhost:4567/" +
Hashing.murmur3_32().hashString(request.queryParams("url"), StandardCharsets.UTF_8).toString();
Map<String, Object> model = new HashMap<>();
if(!urls.containsKey(shortURL)) {
model.put("url", shortURL);
urls.put(shortURL, request.queryParams("url"));
return freeMarkerEngine.render(new ModelAndView(model, "shortener.ftl"));
}
model.put("url", shortURL);
return freeMarkerEngine.render(new ModelAndView(model, "shortener.ftl"));
});
get("/:url", (request, response) -> {
if(urls.containsKey(request.url()))
response.redirect(urls.get(request.url()));
response.redirect("/");
return null;
});
}
}
Вот и всё. 41 строка кода и мы написали сокращатель ссылок.
Заключение
На этом моменте, я закончу эту немного затянувшуюся «Getting started» статью. Мне очень жаль что в рунете этот проект обошли стороной. Если вам понравится, я продолжу писать о Spark`е и раскрою больше его возможностей.
Ссылки
> Сайт проекта
> Исходные коды SparkJava
Комментарии (52)
bromzh
09.10.2017 16:28На сколько оно легковесно и что с поддержкой стандартов?
Например, есть undertow, часть проекта WildFly (ex. JBoss). Jar весит около мегабайта, чистый сервер ест всего 4Мб хипа. Но самый большой плюс — поддержка стандартов (Servlet 3.1, JSR-356 для вебсокетов).AndreyRubankov
09.10.2017 21:00+2Spark – это сахар поверх jetty: github.com/perwendel/spark/blob/master/pom.xml
Но самый большой плюс — поддержка стандартов (Servlet 3.1, JSR-356 для вебсокетов).
Servlet API – это крутой стандарт, но он уже монстроузорный :-(
Servlet API – это не HTTP стандарт, это абстракция над гораздо большим количеством протоколов. С чистым Servlet API крайне неудобно работать. И над ним делают еще парочку абстракций, чтобы добавить удобства. А потом это все выливается в довольно медленный код и раздутый хип.
Servlet API нужно омолаживать; его уже нужно чем-то заменить! Делать какой-то Java HTTP API.3draven
09.10.2017 22:54-9Бредишь? Очень удобная штука, главно врубиться.
AndreyRubankov
10.10.2017 10:27+3Чистый Servlet API – крайне неудобная вещь.
- Нужно оверрайдить нужные http методы сервлетов (doGet, doPost, etc).
- Нужно приводить ServletRequest в HttpServletRequest.
- Нужно самому руками вычитывать энтити из InputStream и парсить их!!
- Нужно так же делать сериализацию энтитей и писать в OutputStream!!
Где же тут удобство?
Фреймворки и дополнительные уровни абстракций появляются потому, что сервлеты в чистом виде – это боль и унижение.3draven
10.10.2017 13:08-6Точно бредишь :) Я на wildfly держу несколько проектов. Сериализация-десериализация автоматом. Я оперирую только объектами. Реализация методов для запросов — это ООП, родной. Там же наследование и переопределение… для тех кто понимает конечно. С Request я напрямую вообще дел не имею, ибо JSON-API уже изобрели давно.
pmcode
09.10.2017 16:59Мне Jooby больше нравится. Если смотреть на API, то он Spark и не отличишь, но у него: DI (via Guice) из коробки, в разы лучше документация, очень гибкая концепция модулей, поддержка роутинга в стиле JAX-RS, несколько серверов (Netty, Jetty, Undertow) на выбор и другие плюшки. Жалко комьюнити пока не очень большое, Spark поболее раскручен, конечно.
Хотел бы затронуть такую тему как шаблонизаторы. Спарк имеет большую нативную поддержку оных.
А server-side rendering сейчас еще нужен? Как-то казалось что сейчас микрофреймворк это для REST сервиса или для SPA.j_wayne
09.10.2017 17:34Конечно нужен. Я использовал sparkjava для небольшого инфраструктурного сервиса. SPA городить слишком жирно, вывести-то всего пару табличек. В целом, было несколько более трудоемко, по сравнению с большими фреймворками.
worldmind
09.10.2017 22:06Порльзуясь случаем спрошу — а что сейчас в джава мире для клиент-сайда популярно?
Хотелось бы что-то вроде GWT, чтобы делать интерфейс из компонентов и не думать о джаваскрипте, GWT актуален ещё?staticlab
09.10.2017 22:22+2Вроде бы Vaadin сейчас в моде.
nile1
10.10.2017 08:27Vaadin медленный, слишком много логики обрабатывается на стороне сервера.
Нынче в моде React/VueJS/Angular и прочие client-side JavaScript фреймворки.staticlab
10.10.2017 10:57А в JavaScript-фреймворках всё обрабатывается и тормозит на клиенте :3 И вообще, автор хотел обойтись без JS.
voopr
10.10.2017 08:35Если кратко, то нет, не актуален.
На мой взгляд это такой Delphi в java мире. Legacy думаю еще достаточно много, но вот начинать на нем что-то новое на мой взгляд не надо.konsoletyper
10.10.2017 14:45начинать на нем что-то новое на мой взгляд не надо
А можно услышать какие-нибудь аргументы? Просто интересно
justboris
10.10.2017 15:21Потому что он безнадежно отстал от прогресса.
Например
Element.querySelector()
уже более 5 лет доступен в браузерах, а в GWT его так и не поддержали: https://stackoverflow.com/questions/2406002/find-an-element-by-css-selector-in-gwt/3164919
И такие флешбеки при разработке на GWT встречаются регулярно. Хочется заиспользовать какую-нибудь браузерную особенность, а нет – GWT о ней не знает и вызвать не дает. Можно выкрутиться через JSNI, но зачем тогда вообще тащить GWT.
В соседних комментах предлагают Kotlin или Scala.js, в них с актуальностью все получше.
konsoletyper
10.10.2017 15:25+1Например
Element.querySelector()
уже более 5 лет доступен в браузерах, а в GWT его так и не поддержалиА зачем его вызывать вручную? Я всегда думал, что выбор элементов за меня сделает библиотека виджетов. А сделает она это через JSNI или через overlay-тип — мне какая разница? Конечно, иногда надо напрямую сделать что-то с DOM, но это настолько редко требуется, что кусочек JSNI не проблема написать. Зато остальные 99% кода пишутся на Java без проблем.
но зачем тогда вообще тащить GWT
Причина одна, но для кого-то может быть крайне весомой — возможность писать клиентский код на Java.
Throwable
10.10.2017 11:40+1Клиент-сайд состоит из двух частей: фреймворка и библиотеки виджетов. В качестве фреймворка довольно давно использую GWT + GQuery + RestyGWT. Доволен. Из печалек: медленная компиляция и неудобный дебаг в superdevmode. В качестве библиотеки виджетов можно использовать GWTBootstrap, GWT-Material, gwt-polymer-elements, любую CSS-based библиотеку, или даже интегрировать практически любой bullshit.js.
Про упомянутый Vaadin: да, подтормаживает (постоянная синхронизация состояния виджетов с сервером), спорно годится для интернет-проектов, но для энтерпрайзных админок и дэшбоардов — самое то. При помощи GWT можно использовать весь widgetset Vaadin-a на клиентской стороне без использования серверной части, что значительно разгоняет UI. Кроме того, есть полностью js-ный Vaadin Elements. Из печалек: из пресловутый Vaading Grid тормозит гораздо больше на клиенте, чем на сервере.
P.S. Хочу попробовать подергать Kotlin.js. Кстати, если уж о котлине заговорили, то вот фреймворк со схожим функционалом: https://github.com/Kotlin/ktor
webkumo
10.10.2017 14:02GWT актуален ещё
Зачем вам этот тормозной монстр? Имхо проще отдельного фронтэндера иметь, чем работать с GWT...
3draven
10.10.2017 14:39Удваиваю ненужность GWT и прочих. У меня коллега интерфейсы шпарит махом на jquery чистом. Я только REST подгоняю, который JSON выдает и принимает, и все.
konsoletyper
10.10.2017 14:35+2Пользуясь случаем, попиарю свой проект: http://teavm.org/
Поддерживаются Java, Scala, Kotlin, есть Angular-образный фреймворк в комплектеworldmind
10.10.2017 18:12А насколько он аналогичен? GWT в теории хорош тем что:
1. Не надо писать на джаваскрипте
2. Можно делать гуй на высоком уровне — есть готовые компонентыkonsoletyper
10.10.2017 18:27+1- В TeaVM не надо писать на JavaScript, для того он и создан
- Есть возможность делать гуй на шаблонах (с биндингом, перерисовкой только изменившегося DOM). Пишите шаблоны под bootstrap — будут и компоненты. Можно создавать свои компоненты для шаблонизатора, можно биндиться к JavaScript-компонентам (как и в GWT). Но, разумеется, т.к. мой проект пока не нашёл широкого применения, под него есть только небольшой набор стандартных компонентов.
Throwable
11.10.2017 17:10Я думал, Вы забросили этот проект. Ан-нет, уже даже достаточно юзабельная версия! На выходных попробую. Из пожеланий: добавить автоматический генератор врапперов к JS-библиотекам из TypeScript на DefinetlyTyped.
foal
10.10.2017 16:15+1Вполне актуален. Последняя версия поддерживает практически все фичи из Java 8. Java 9 как компилятор обещают скоро, фичи из Java 9 чуть позже. Как и колега, иcпользую GWT + GQuery + Gin + RestyGWT, хотя и посматриваю на Dagger ‡ и RxJava. В общем, доволен как слон :)
nwalker
09.10.2017 18:18+1Мне всегда было интересно, как создатели таких проектов предполагают жить без reverse URL resolution.
AndreyRubankov
09.10.2017 21:09reverse URL resolution.
А не могли бы вы рассказать, что это за зверь такой?
ps: за 5+ лет еще не приходилось сталкиваться с этим термином; может эта фича не столь и нужная?staticlab
09.10.2017 22:02+1Это когда фреймворк даёт возможность сгенерировать URL для ссылки, сославшись на соответствующий роут по имени и передав нужные параметры.
Например, в Рельсах:
link_to "Мой профиль", @profile # @profile — переменная с экземпляром класса Profile
генерирует:
<a href="/profiles/1">Мой профиль</a>
AndreyRubankov
10.10.2017 10:14Звучит, как удобная фича с удобством для поддержки – меняешь роут, меняется и реверс урл.
Но для того, чтобы она работала, нужно чтобы фреймворк на себя брал очень много обязанностей, как минимум фреймворку нужно, чтобы он знал все его роуты по именам; а чтобы это все корректно работало, то еще и по параметрам (PathParams, QueryParams, etc), т.е. сам фреймворк уже становится раздутым и сложным.
Часто в этом даже нету необходимости: роуты практически никогда не меняются после релиза. А если меняются, то на старый вешается редирект, чтобы не сломать ничего.
Это без сомнения крутая фича, но в микрофреймворке она избыточна. Если нужно этот функционал можно прикрутить через стороннюю библиотеку.
j_wayne
10.10.2017 11:12В JVM-фреймворках с этим, к сожалению, печально.
Искал JVM замену рельсам.
Rails-alike фреймворки существуют, но почему то они не переняли некоторых удобных деталей.
Эти самые URL, миграции, routes.rb с resources :foo, only: [:index, :show], лейауты. Разве что грусть-тоска по asset pipeline отпадает сама собой с появлением webpack и подобных.
jruby on rails сначала показались находкой. Но jruby быстро разочаровала страшными тормозами, очень сильная деградация в зависимости от количества библиотек/кода. TDD-ить невозможно. Прелоадеры глючат. Боль короче)beduin01
10.10.2017 14:46+1Посмотрите vibed.org
j_wayne
10.10.2017 15:00Спасибо. Забавно конечно, но не JVM (есть 3rd party библиотека, ради которой весь сыр-бор). А если устраивать межпроцессное взаимодействие а-ля микросервисы, проще взять те же рельсы и не мучиться. Да и не все привычные фичи в vibe.d есть. Не нашел route resources и миграций например.
rraderio
10.10.2017 16:27-1Play Framework это RoR в JVM www.playframework.com/documentation/2.6.x/JavaRouting#Reverse-routing
j_wayne
10.10.2017 16:51Да, и почему-то роуты описываются индивидуально.
Нельзя взять и объявить resources :users скажем (такая фича есть разве что в grails, но до лаконичности рельсовой версии ей далеко).
Зачем это нужно? Проще менять роуты.
Database Evolutions не предоставляет никакого обобщенного DSL, нужно писать сырой SQL.
Да, контроля больше. Но с другой стороны, в 95% случаях мне хватало и рельсового DSL, а если уж не хватает, ничего не мешает (почти — автоотката все же не будет) написать специфичный сырой SQL.
Не покидает ощущение недоделанности. При всех недостатках рельсов, у них есть основная идея — удобство разработчика (да, я понимаю, зачастую в ущерб каким-то другим вещам). И вот эту идею в JVM-фреймворках реализуют, на мой взгляд, не в полной мере.
P.S. я разрабатываю и на RoR и на Java, т считаю, что RoR в JVM можно назвать лишь ее же но на jruby. Однако мне она не подошла — см. коммент выше.rraderio
10.10.2017 17:28Зачем это нужно? Проще менять роуты.
Ну а в Play есть автокомплит для роутов и они проверяются во время компиляции.
Database Evolutions
Можно взять Liquibase или другую библиотеку
nwalker
09.10.2017 22:35Я так и не нашел какого-то единого устоявшегося термина, но да, это возможность сгенерировать ссылку, исходя из обработчика или идентификатора роута и параметров.
Пример из рельсов несколько чересчур магический и высокоуровневый, но суть верно передает.
wert_lex
09.10.2017 21:00Я понимаю, что это сильно за пределами вводной статьи, но это какой-то очень уж сферический пример. Поэтому у меня вопросы:
- как это все работает, когда тело метода
handle()
требует асинхронных операций. В базу, например сходить. И, поскольку Java многопоточная, то как обстоят дела с конкурентным доступом к одному и тому же request/response из разных потоков. - как использовать композицию? Например, накрутить кастомную авторизацию, чтобы сбегать в базу и найти пользователя по сессии.
akamensky
10.10.2017 06:20Я вот как раз такими же вопросами задался некоторое время назад и из этого родилось вот такое чудовище, там точно так же как и в экспрессе можно сделать чтобы несколько handlers отработали в определенном порядке.
- как это все работает, когда тело метода
rraderio
10.10.2017 16:21+1Хотел бы затронуть такую тему как шаблонизаторы. Спарк имеет большую нативную поддержку оных
А Rocker поддерживается?
github.com/fizzed/rocker
VladlenBronislav
11.10.2017 15:18Вкратце, можно ли объяснить:
1. Можно ли обойтись без Guava?
2. В чем необходимость использования «хэш-функция Murmur3»?
3. Можно ли Guava заменить на коллекцию из cuncerrent?
Спасибо за овтеты.mat3 Автор
11.10.2017 16:381. Вполне
2.Нам Guava нужна, потому что в ней реализована хэш-функция Murmur3. Весь плюс в том что она достаточно сильна устойчива к коллизиям.
. Мы используем её для сокращение ссылки.
3. Я и так не использую коллекции из гуавы
Crandel
Интересно, как легко гуглить вопросы по этому микрофреймворку? Ведь есть еще Apache Spark, а Big Data сейчас в почете
pmcode
Гуглить по sparkjava. Да, трудно, пока собирал про него информацию измучался.
Crandel
Почему же разработчики не переименуют проект? Для таких микрофреймворков это не должно быть проблемой
grossws
Я, честно говоря, не помню, подходили ли Apache Spark PMC к товарищам из sparkjava, но PMC не сильно парится насчёт совпадения названия, т. к. существенно разные области и это не мешает трейдмарку Apache Spark.
Crandel
Я не думаю, что это будет каким-то образом мешать Apache Spark, но я сейчас работаю над проектом Apache Spark с Java API и при гуглении spark java мне этот фреймворк ниразу не попадался. Хотя может это гугл такой умный, фильтрует поиск для меня
mat3 Автор
Я гуглил spark web framework.
Matvey-Kuk
Cisco Spark, Chevrolet Spark…
Crandel
Не согласен, не пересекающиеся ниши, а вот сабж и Apache Spark — оба фреймворка имеют Java API
eignatik
Участвовал в двух проектах со спарком как-то. В целом не было проблемой поискать в гугле. Просто запросы писал поподробнее. На самом деле искать что-то по нему вполне можно)