Простой webdev на Java 8

в 15:24, , рубрики: jade4j, java, java8, ORMLite, sparkjava, web-разработка, фрейморки java

Хочу рассказать о том, как мне пришла в голову идея заняться веб-разработкой на 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js