Все началось с того, что я решил написать свою Java Common библиотеку. Очень часто для типичных задач на Stack Overflow находятся решения в 3-5-10 строк кода. Копи-пастить себе в проекты надоело. Решил вынести это в отдельную библиотеку, которую можно сделать Open Source и использовать в текущих и будущих своих проектах. Безусловно есть такие хорошие либы как Google Guava и Apache Common, которые я тоже использую в работе, но они достаточно «правильные» и гибкие, что выливается не в одну строчку кода.
В итоге вечерами дома в свободное от работы время я покрыл за 1 неделю следюущие направления: Sleep/Delay/Pause, Timer for Benchmark, Random range generator, File operations, Tasks/Threads, Reflection, JSON, URL, Logging, Strings. Смотрел свои проекты на предмет копи-паста стандартных решений и писал решения в библиотеку. В очередной раз применил TDD подход для разработки библиотеки. Сначала пишешь тест на не существующие классы и методы, а потом реализуешь код, что бы тесты стали зелеными. Решает две проблемы: во-первых ты пытаешься удобно использовать свои классы и их методы до их реализации, во-вторых у тебя остаются тесты, которые в будущем могут свалиться и ты поймешь, что у тебя сломалось при очередном рефакторенге или багфиксе.
Дальше, больше. Я начал анализировать, как я и многие другие, типично использую Spring/JBoss и понял, что legacy и широта возможности все усложняет. Можно реализовать упрощенный типичный Dependency Injection. Сказано, сделано. Добавил в свою билиотеку DI Framework. Мои знакомые смотрели мою реализацию и говорили что разобраться, как устроен Spring просто нереально, там полная жесть наследований и обверток, а у тебя все видно прям на первом уровне реализации и все понятно. Им было интересно, как работать с аннотациями и тд.
Реализовав DI Framework я задумался над тем, что еще чуть-чуть и будет полноценный Enterprise Server. Осталось добавить ORM и Web-сервер с MVC, REST и security. Все в лучших традициях, так сказать. И меня затянуло. Еще неделька вечерами после работы, ссоры с женой, и получился Simplified Enterprise Server. Я не придерживался стандартов JavaEE, так как писал, как бы мне казалось, было удобно и понятно использовать. Сам я на работе использую Spring Boot, Spring Data, JPA 2.0, Spring MVC, Spring Rest, Spring Security. До этого делал проект на JBoss, видел другую сторону JEE, так сказать. Но вся это универсальность и гибкость конечно в тему. Но когда тебе нужно быстро накидать прототип в стиле JEE или тебе нужно научится кодить серьездные проекты на Java, окунаться в мир Spring, Hibernate и т.д. долго и кропотливо. Единственная альтернатива это Spring Boot, но реально там много происходит скрыто от тебя и если ты не знаешь как работает Spring под капотом, это только тебя тормозит, так как любой шаг в лево или в право это полный нырок в детали…
В итоге, код фреймворка на гитхабе github.com/evgenyigumnov/common
Пример веб-сервиса использующего этот фреймворк на гитхабе github.com/evgenyigumnov/example и в онлайне его тоже можно посмотреть java.igumnov.com:8181 Пользователь: demo Пароль: demo
Структура примера:
./: pom.xml ./javascript: user.js ./pages: index.html layout.html login.html ./sql: 1.sql ./src/main/java/com/igumnov/common/example: App.java ExampleUser.java
pom.xml
<dependencies> <!-- подключаем наш фреймворк --> <dependency> <groupId>com.igumnov</groupId> <artifactId>common</artifactId> <version>3.15</version> </dependency> <!-- подключаем БД --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.187</version> </dependency> <!-- подключаем Bootstrap, AnglularJS и тд из webjars проекта --> <dependency> <groupId>org.webjars</groupId> <artifactId>angular-ui-bootstrap</artifactId> <version>0.12.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>angularjs</artifactId> <version>1.3.8</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.1</version> </dependency> </dependencies>
App.java
public class App { public static void main(String[] args) throws Exception { ORM.connectionPool("org.h2.Driver", "jdbc:h2:mem:test", "SA", "", 1, 3); // Создаем пул коннекций к БД (максимум 3 коннекта) ORM.applyDDL("sql"); // Накатываем на базу объявления таблиц или оно это пропускает если уже далало WebServer.init("localhost", 8181); // Задаем начальные параметры веб-сервера WebServer.security("/login", "/login", "/logout"); // Говорим что у нас включена безопасность которая должна работать по URL-ам WebServer.addRestrictRule("/*", new String[]{"user_role"}); // Ограничиваем доступ только для пользователям с ролью user_role WebServer.addAllowRule("/static/*"); // Даем доступ для всех к статическому контенту WebServer.addClassPathHandler("/static", "META-INF/resources/webjars"); // Указываем откуда брать этот статический контент из classpath от webjars WebServer.addAllowRule("/js/*"); // Даем доступ для всех к нашим Java Script-ам WebServer.addStaticContentHandler("/js", "javascript"); // Указываем в какой папке на винте лежат наши Java Script WebServer.addTemplates("pages",0); // Указываем в какой папке на винте лежат шаблоны страниц // Добавляем контроллер по урл "/", который добавляет в модель текущее время и говорит, что нужно отобразить index.html WebServer.addController("/", (request, model) -> { model.put("time", new Date()); return "index"; }); // Добавляем контроллер по урл "/login", который говорит, что нужно отобразить login.html WebServer.addController("/login", (request, model) -> { return "login"; }); // Добавляем REST-контроллер по урл "/rest/user" и указываем что могут методом POST/PUT прислать JSON-объект типа ExampleUser.class WebServer.addRestController("/rest/user", ExampleUser.class, (request, postObj) -> { switch (request.getMethod()) { case (WebServer.METHOD_GET): // Прилетел GET запрос ArrayList<Object> users; try { users = ORM.findAll(ExampleUser.class); // Извлекаем список пользователей } catch (Exception e) { throw new WebServerException(e.getMessage()); // Словили ошибку, которая будет сериализована в JSON } return users; // Возвращаем массив пользователей который сам сериализуется в JSON case (WebServer.METHOD_POST): // Прилетел POST запрос ExampleUser ret = null; try { ret = (ExampleUser) ORM.insert(postObj); // Вставляем его в БД } catch (Exception e) { throw new WebServerException(e.getMessage()); } return ret; // В случае успеха просто возвращаем добавленного пользователя в виде JSON case (WebServer.METHOD_DELETE): // Прилетел DELETE запрос ExampleUser user; try { user = (ExampleUser) ORM.findOne(ExampleUser.class, request.getParameter("userName")); // Ищем юзера в БД if(user.getUserName().equals("demo")) { // Если юзер demo не даем удалять throw new WebServerException("You cant delete user demo"); } else { ORM.delete(user); // Иначе шлем в БД delete-запрос } } catch (Exception e) { throw new WebServerException(e.getMessage()); // Словили ошибку, которая будет сериализована в JSON } return user; // Возвращаем JSON юзера, которого удалили в случае успеха данной операции default: throw new WebServerException("Unsupported method"); // Ругаемся если прилетел запрос иного типа, например PUT или иной } }); ArrayList<Object> users = ORM.findAll(ExampleUser.class); // Берем из БД всех пользователей if (users.size() == 0) { // В таблице с пользователями пусто ExampleUser user = new ExampleUser(); user.setUserName("demo"); user.setUserPassword("demo"); ORM.insert(user); // Добавляем demo/demo пользователя в БД WebServer.addUser("demo", "demo", new String[]{"user_role"}); // Сообщаем веб-серверу что есть пользователь demo/demo с ролью user_role } users.stream().forEach((user) -> { // Перебираем список пользователей полученный из БД ExampleUser u = (ExampleUser) user; WebServer.addUser(u.getUserName(), u.getUserPassword(), new String[]{"user_role"}); // Сообщаем веб-серверу о новом пользователе с ролью user_role }); WebServer.start(); // Если до этого места кода дошло управление и ничего не вывалилось по Exception, то стартуем веб-сервер :) } }
ExampleUser.java
// Данный класс используется для JSON сериализации и десериализации и также для меппинга в БД public class ExampleUser { @Id(autoIncremental = false) // Необходимо ORM знать какое поле является Primary Key и генерится ли при insert значение этого поля private String userName; private String userPassword; ... }
1.sql
# Создаем таблицу в БД где будем хранить через ORM объекты типа ExampleUser.class CREATE TABLE ExampleUser (userName VARCHAR(255) PRIMARY KEY, userPassword VARCHAR(255))
login.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd"> <!-- Указываем что нужно использовать декоратор layout из layout.html --> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout"> <body> <!-- Объявляем наш контент блок который будет подставлен в layout.html --> <div layout:fragment="content"> <form name="form" action="/j_security_check" method="POST"> <div class="modal-header"> <h3 class="modal-title">Login</h3> </div> <div class="modal-body"> <div class="form-group"> <input type="text" name="j_username" class="form-control" value="" placeholder="Login"/> </div> <div class="form-group"> <input type="password" name="j_password" class="form-control" placeholder="Password"/> </div> <div class="form-group"> <button type="submit" id="login" class="btn btn-primary">OK</button> </div> </div> </form> </div> </body> </html>
index.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd"> <!-- Указываем что нужно использовать декоратор layout из layout.html --> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout"> <body> <!-- Объявляем наш контент блок который будет подставлен в layout.html --> <div layout:fragment="content"> <!-- Подключаем наш контроллер на AngularJS--> <script src="/js/user.js"></script> <h1 th:text="${time}"></h1> // Выводим текущее время переданное в модель <!-- Обозначаем область действия нашего контроллера UserCtrl --> <div ng-controller="UserCtrl"> <table class="table"> <thead> <tr> <th>Name</th> <th>Password</th> <th></th> </tr> </thead> <tbody> <!-- В цикле заполняем таблицу пользователями --> <tr ng-repeat="user in users"> <td>{{user.userName}}</td> <td>{{user.userPassword}}</td> <!-- По клику на крестик вызываем функцию на контроллере для удаления пользователя --> <td><a href="#"><span class="glyphicon glyphicon-remove" tooltip="Delete" ng-click="deleteUser(user)"/></a></td> </tr> </tbody> </table> <div ng-model="user"> <!-- Форма добавлени пользователя --> <div class="form-group"> <input type="text" class="form-control" ng-model="user.userName" placeholder="Login"/> </div> <div class="form-group"> <input type="password" class="form-control" ng-model="user.userPassword" placeholder="Password"/> </div> <div class="form-group"> <!-- По клику на кнопке вызываем функцию в контроллере добавляющую пользователя --> <button class="btn btn-primary" ng-click="addUser(user)">Add</button> </div> </div> </div> </div> </body> </html>
layout.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd"> <!-- Область действия нашего приложения на AngularJS --> <html ng-app="com.igumnov.common.example"> <head> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/3.3.1/css/bootstrap.min.css" /> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <script src="/static/angularjs/1.3.8/angular.min.js"></script> <script src="/static/angularjs/1.3.8/angular-resource.min.js"></script> <script src="/static/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js"></script> <div class="container"> <!-- Сюда будет вставляться контентный блок --> <div layout:fragment="content"></div> </div> </body> </html>
user.js
angular.module('com.igumnov.common.example', ['ui.bootstrap', 'ngResource']) .factory('User', ['$resource', function ($resource) { // Объявляем REST-ресурс User return $resource('/rest/user', {}, { list: { // Список юзеров method: 'GET', cache: false, isArray: true // Результат вызова массив }, add: { // Добавляем юзера method: 'POST', cache: false, isArray: false // Результат вызова один объект }, delete: { // Удаляем юзера method: 'DELETE', cache: false, isArray: false // Результат вызова один объект } }); }]) .controller('UserCtrl', function ($scope, User) { // Обьявляем наш контроллер UserCtrl $scope.users = User.list({}); // Заполняем список пользователя при инициализации контроллера $scope.addUser = function (user) { // Функция добавления пользователя User.add({},user,function (data) { // Дергаем REST-интерфейс $scope.users = User.list({}); // В случае успеха, перезаполняем список пользователей }, function (err) { alert(err.data.message); // В случае ошибки, выводим ошибку }); } $scope.deleteUser = function (user) { // Функция удаления пользователя User.delete({"userName" : user.userName},user,function (data) { // Дергаем REST-интерфейс $scope.users = User.list({}); // В случае успеха, перезаполняем список пользователей }, function (err) { alert(err.data.message); // В случае ошибки, выводим ошибку }); } });
В заключении, буду рад любой критике и предложению по улучшению кода библиотеки. Для себя я получил профит в разминании мозга при написание библиотеки и использовании замыканий/лямбд. Иногда скучно писать коммерческие продукты, хочется создать свой космический корабль. Не стисняйтесь форкать мою либу и самим ее модифицировать под свои нужды. Он достаточно проста и легка для внесения в нее модификаций. Буду признателен, если вы будете присылать пулл реквесты, что бы ваши доработки вносились в библиотеку. Я лично настроен достаточно быстро их проверять и принимать. Я просто фонатик-программер, меня это втыкает. Люблю кодить!
PS Да-да, я не люблю писать javacode, шлите пулл реквесты с ним, сейчас по коду либы очень понятно, что каждый метод ее делает…
ссылка на оригинал статьи http://habrahabr.ru/post/259005/
Добавить комментарий