Хочу рассказать о том, как мне пришла в голову идея заняться веб-разработкой на Java.
Итак, как только я созрел для этого дела, составил небольшой список, которому должны соответствовать выбранные мною инструменты разработки:
- Сборка при помощи maven;
- Простой процесс деплоя и запуска;
- Библиотеки должны быть легковесны;
- Возможность использования шаблонизатора.
Усиленно погуглив, я нашел то, что мне нужно: Spark, jade4j и OrmLite.
Итак, инструменты выбраны, рассмотрим каждый из них в отдельности.
Spark Framework
Я выбрал последнюю версию, для работы которой требуется Java 8. Сам фреймворк позиционируется как Sinatra-inspired, так как я с синатрой знаком не слишком близко, он мне скорее напомнил Express для Node.js.
Идея была близка, тем более что позитивный опыт работы с Express уже был. Документация кажется бедноватой, но в процессе разработки убеждаешься, что её более чем достаточно. Большим его плюсом я считаю и то, что для разработки не требуется предварительная настройка окружения, установка контейнера сервлетов и так далее. Всё, что нужно — это добавить зависимость в pom.xml, написать пару строк — и всё взлетит.
Jade4j
Если вы работали с Node.js шаблонизаторами, в частности с jade, то вам наверняка захочется его использовать ещё. Легкочитаемый код, хорошая документация, множество красивых фич и опыт использования ранее заставили меня остановить свой выбор именно на этом шаблонизаторе.
Небольшой пример layout.jade:
doctype html
html
head
block headsection
body
header
div#main
block content
footer
Элементы {block blockname} реализуем в других файлов таким образом:
extends layout
block headsection
title Привет!
block content
h1 Hello, habr!
OrmLite
Скорее всего, некоторые андроид-разработчики хорошо знакомы с этой ORM. Она проста в использовании, легковесна и конфигурируется аннотациями.
Вот простой пример pojo класса Пользователь:
package com.vagga.pojo;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
@DatabaseTable(tableName = "users")
public class User {
@DatabaseField(columnName = "user_id", generatedId = true)
private int userId;
@DatabaseField(columnName = "username", unique = true, canBeNull = false)
private String userName;
@DatabaseField(columnName = "user_pass")
private String password;
public User() {
}
/*
* Далее геттеры-сеттеры
*/
}
Взаимодействие с БД может осуществляться через встроенный в ORM Dao. Я написал небольшой синглтон-класс, чтобы упростить себе жизнь (возможно, код ниже вас немного расстроит, поэтому оговорюсь ещё раз: главной целью ставилась простота реализации и использования):
package com.vagga.utils;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.vagga.pojo.Category;
import com.vagga.pojo.Comment;
import com.vagga.pojo.Post;
import com.vagga.pojo.User;
import java.sql.SQLException;
public class DbUtil {
private static DbUtil ourInstance = new DbUtil();
private JdbcConnectionSource dbSource;
private Dao<User, Integer> userDao;
private Dao<Post, Integer> postDao;
private Dao<Category, Integer> categoryDao;
private Dao<Comment, Integer> commentDao;
public static DbUtil getInstance() {
return ourInstance;
}
private DbUtil() {
try {
dbSource = new JdbcConnectionSource("jdbc:mysql://localhost:3306/vagga?user=root&characterEncoding=utf8");
userDao = DaoManager.createDao(dbSource, User.class);
postDao = DaoManager.createDao(dbSource, Post.class);
categoryDao = DaoManager.createDao(dbSource, Category.class);
commentDao = DaoManager.createDao(dbSource, Comment.class);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("Cannot establish DB connection " + e.getMessage() + " " + e.getCause());
}
}
public Dao<User, Integer> getUserDao() {
return userDao;
}
public Dao<Post, Integer> getPostDao() {
return postDao;
}
public Dao<Category, Integer> getCategoryDao() {
return categoryDao;
}
public Dao<Comment, Integer> getCommentDao() {
return commentDao;
}
}
Несколько решений, которые, возможно, вас удручат
Начав процесс кодинга, я наткнулся на несколько проблем, которые надо было решать каким-либо путём.
Проблемой номер один было то, что Spark из коробки не поддерживал jade, поэтому пришлось гуглить и разбираться, как их подружить. Решение проблемы оказалось достаточно простым. Нужно просто отнаследоваться от класса TemplateEngine и реализовать интерфейсный метод render.
В той же гуглогруппе я нашел вот этот вполне рабочий код:
package com.vagga.utils;
import de.neuland.jade4j.JadeConfiguration;
import de.neuland.jade4j.exceptions.JadeException;
import de.neuland.jade4j.model.JadeModel;
import de.neuland.jade4j.template.FileTemplateLoader;
import de.neuland.jade4j.template.JadeTemplate;
import de.neuland.jade4j.template.TemplateLoader;
import spark.ModelAndView;
import spark.TemplateEngine;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
public class JadeEngine extends TemplateEngine {
private JadeConfiguration configuration;
private String directory = new File(".").getCanonicalPath();
public JadeEngine() throws IOException {
this.configuration = new JadeConfiguration();
this.directory = this.directory + "/src/main/resources/templates/";
TemplateLoader loader = new FileTemplateLoader(directory, "UTF-8");
configuration.setTemplateLoader(loader);
}
@SuppressWarnings("unchecked")
@Override
public String render(ModelAndView modelAndView) {
StringWriter stringWriter = new StringWriter();
try {
JadeTemplate template = this.configuration.getTemplate(modelAndView.getViewName());
JadeModel jadeModel = new JadeModel((Map<String, Object>) modelAndView.getModel());
template.process(jadeModel, stringWriter);
} catch (JadeException | IOException e) {
e.getCause();
}
return stringWriter.toString();
}
}
Следующая проблема была связана с тем, что я не знал, как толком разбить маппинг запросов на разные классы. В результате я решил создать интерфейс с тремя методами:
package com.vagga.routes;
public interface BaseRoute {
public void initBeforeAction();
public void initActions();
public void initAfterActions();
}
Далее я создал базовый класс маршрута, в котором проинициализировал хелперы рендеринга шаблонов:
package com.vagga.routes;
import com.vagga.utils.JadeEngine;
import com.vagga.utils.JsonTransformer;
import java.io.IOException;
public class Route {
protected JadeEngine templateEngine;
protected JsonTransformer jsonTransformer;
protected Route() throws IOException {
templateEngine = new JadeEngine();
jsonTransformer = new JsonTransformer();
}
}
Реализация конечного класса-маппера тут:
package com.vagga.routes;
import com.vagga.pojo.Category;
import com.vagga.pojo.User;
import com.vagga.utils.DbUtil;
import spark.ModelAndView;
import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static spark.Spark.*;
public class HomeRoute extends Route implements BaseRoute {
public HomeRoute() throws IOException {
super();
}
@Override
public void initBeforeAction() {
}
@Override
public void initActions() {
get("/", (req, res) -> {
Map<String, Object> model = new HashMap<String, Object>();
try {
List<Category> categories = DbUtil.getInstance().getCategoryDao().queryForAll();
User user = DbUtil.getInstance().getUserDao().queryForId(1);
model.put("user", user);
model.put("categories", categories);
model.put("title", "Главная");
} catch (SQLException e) {
e.printStackTrace();
}
return new ModelAndView(model, "home/index.jade");
}, this.templateEngine);
}
@Override
public void initAfterActions() {
}
}
Теперь все классы-мапперы надо как-то инстанциировать. Я это делаю прямо в main классе, ибо ничего умнее пока не придумал (буду благодарен, если кто-нибудь наведет на мысль как это можно сделать лучше):
public class VaggaMain {
public static void main(String[] args) {
try {
ArrayList<BaseRoute> routes = new ArrayList<>();
routes.add(new HomeRoute());
routes.add(new AdminRoute());
routes.add(new ApiRoute());
routes.forEach((route) -> {
route.initBeforeAction();
route.initActions();
route.initAfterActions();
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
Итог
Все цели, которые я перед собой поставил, были реализованы. Инструментами остался доволен. Основные проблемы, с которыми столкнулся — это крайняя скудность информации в интернете о вышеуказанных инструментах. А вообще — фреймворк классный, простой и заставляет радоваться.
Всем спасибо за внимание! Конструктивная критика приветствуется.
Автор: DanteXXI