
Введение
С момента первого релиза прошло достаточно много времени (ссылка на предыдущую статью). Что изменилось?
- улучшена стабильность системы в целом;
- реализована ленивая загрузка компонентов;
- встроена базовая система слушателей;
- встроена поддержка аспектно-ориентированного программирования (для средней сложности решения задач, в остальном все же советую использовать — AspectJ библиотеку)
- новый загрузчик RequestFactory
- встроена работа с кешем на базе EhCache, Guava
- встроена работа с потоками (как инициализация посредством аннотации @SimpleTask, так и прямая работа с пулом)
**Модули
- модуль работы с базой (легковесный ORM с поддержкой JPA, Transactions, NO-SQL Driver — Orient, Crud methods, repository system и автогенерацией запросов из функции класса-репозитория)
- модуль работы с веб-мордой (маппинг линков посредством аннотаций, поддержка кастомных producers/consumes, Velocity Template Rendering Page, Basic Security Requests, Sessions, Cookies, SSL) на базе Netty 4.1.30.Final
Структура фреймворка

"Это конечно же все хорошо,"- скажите Вы, -"но по факту работает ли это все?".
"Да, работает. Прошу под кат".
Процесс реализации примера
Для реализации примера я буду использовать Maven 3 и Intelijj Idea 2018.2.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>example-webapp</artifactId> <groupId>org.ioc</groupId> <packaging>jar</packaging> <version>0.0.1</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <.source>1.8</source> <target>1.8</target> </configuration> <executions> <execution> <id>default-testCompile</id> <phase>test-compile</phase> <goals> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.ioc</groupId> <artifactId>context-factory</artifactId> <version>2.2.4.STABLE</version> </dependency> <dependency> <groupId>org.ioc</groupId> <artifactId>orm-factory</artifactId> <version>2.2.4.STABLE</version> </dependency> <dependency> <groupId>org.ioc</groupId> <artifactId>web-factory</artifactId> <version>2.2.4.STABLE</version> </dependency> </dependencies> <repositories> <repository> <id>context</id> <url>https://raw.github.com/GenCloud/ioc_container/context</url> </repository> <repository> <id>cache</id> <url>https://raw.github.com/GenCloud/ioc_container/cache</url> </repository> <repository> <id>threading</id> <url>https://raw.github.com/GenCloud/ioc_container/threading</url> </repository> <repository> <id>orm</id> <url>https://raw.github.com/GenCloud/ioc_container/orm</url> </repository> <repository> <id>web</id> <url>https://raw.github.com/GenCloud/ioc_container/web</url> </repository> </repositories> </project>
**На maven central пока не переехал, увы.
Структура проекта:

Стандартный MVC паттерн, не правда ли?
Создадим точку входа в приложение:
package org.examples.webapp; import org.ioc.annotations.context.ScanPackage; import org.ioc.annotations.modules.CacheModule; import org.ioc.annotations.modules.DatabaseModule; import org.ioc.annotations.modules.ThreadingModule; import org.ioc.annotations.modules.WebModule; import org.ioc.context.starter.IoCStarter; @WebModule @CacheModule @ThreadingModule @DatabaseModule @ScanPackage(packages = {"org.examples.webapp"}) public class AppMain { public static void main(String[] args) { IoCStarter.start(AppMain.class); } }
**Пояснения:
Аннотация @ScanPackages — определяет контексту пакеты для выявления компонентов (в простонародии — "бинов").
Аннотация @WebModule — служит для подключения и инициализации web фабрики.
Аннотация @CacheModule — служит для подключения и инициализации фабрики кеша, используется для корректной работы ORM (в будущих версиях аннотация не будет требоваться).
Аннотация @ThreadingModule — служит для подключения и инициализации фабрики потоков, используется для корректной работы web фабрики (в будущих версиях аннотация не будет требоваться).
Аннотация @DatabaseModule — служит для подключения и инициализации фабрики ORM.
Все фабрики имеют дефолтные конфигураторы, которые можно изменить на свои с переопределением функций использующихся настроек фабриками (в каждой аннотации модуля переопределен класс конфигуратор — Class<?> autoConfigurationClass() default WebAutoConfiguration.class), либо же отключить любую конфигурацию посредством аннотации @Exclude в main классе.
Утилита IoCStarter — главный класс-инициализатор контекста.
Ну вроде бы все и готово, контекст инициализируется, веб работает на дефолтном порту 8081, но линковки нет и при переходе на сайт нам ничего толком не выдает. Что ж, идем дальше.
Создадим конфигурационный файл для наших модулей. По дефолту все конфиги грузятся из {working_dir}/configs/default_settings.properties — его и создадим по соответствующему пути.
# Threading ioc.threads.poolName=shared ioc.threads.availableProcessors=4 ioc.threads.threadTimeout=0 ioc.threads.threadAllowCoreTimeOut=true ioc.threads.threadPoolPriority=NORMAL # Event dispather # кол-во дескрипторов (процессоров) для обработки слушателей (асинхронное выполнение) ioc.dispatcher.availableDescriptors=4 # Cache # фабрика кеша (EhFactory|GuavaFactory) cache.factory=org.ioc.cache.impl.EhFactory # Datasource # тип базы (локальная-собственная, локальная-серверная или удаленная) #LOCAL, LOCAL_SERVER, REMOTE datasource.orient.database-type=LOCAL # местонахождение базы datasource.orient.url=./database # имя базы данных (для локальной не обязательно) datasource.orient.database=orient # пользователь базы datasource.orient.username=admin # пароль пользователя datasource.orient.password=admin # конфигурация для маппинга сущностей в базу (create, dropCreate, refresh, none) datasource.orient.ddl-auto=dropCreate # конфигурация сообщающая менеджеру, показывать сгенерированные запросы или нет datasource.orient.showSql=true # Web server # порт работы веб сервера web.server.port=8081 # нужен ли SSL обработчик web.server.ssl-enabled=false # in seconds # таймаут сессий (дефолтный 7200 сек. = 2 часа) web.server.security.session.timeout=300 # кодировки веб-морды web.server.velocity.input.encoding=UTF-8 web.server.velocity.output.encoding=UTF-8 # загрузчик веб-морды web.server.velocity.resource.loader=file # класс загрузчика web.server.velocity.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader # путь к находящейся веб-морде web.server.velocity.resource.loading.path=./public
Далее, нам нужны сущность пользователя и ее управляющий репозиторий:
Реализация сущности TblAccount:
package org.examples.webapp.domain.entity; import org.ioc.web.security.user.UserDetails; import javax.persistence.*; import java.util.Collections; import java.util.List; @Entity public class TblAccount implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private long id; @Column(name = "username") private String username; @Column(name = "password") private String password; @Transient private String repeatedPassword; public String getRepeatedPassword() { return repeatedPassword; } public void setRepeatedPassword(String repeatedPassword) { this.repeatedPassword = repeatedPassword; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public List<String> getRoles() { return Collections.singletonList("ROLE_USER"); } }
**Пояснения:
Все стандартно как и у всех JPA-поддерживающих фреймворков. Маппим сущность с помощью @.Entity, создаем Primary Key с помощью аннотации @.Id, маппим колонки с помощью аннотации @Column. и унаследуемся от UserDetails для идентификации сущности в Security модуле.
Реализациия репозитория сущности TblAccountRepository:
package org.examples.webapp.domain.repository; import org.examples.webapp.domain.entity.TblAccount; import org.ioc.annotations.context.IoCRepository; import org.ioc.orm.repositories.CrudRepository; import javax.transaction.Transactional; @IoCRepository public interface TblAccountRepository extends CrudRepository<TblAccount, Long> { @Transactional TblAccount findByUsernameEq(String username); }
**Пояснения:
Аннотация @IoCRepository — служит для определения контекстом, что класс является репозиториеми его нужно обработать "по-другому".
Поддерживает стандартные CRUD функции:
- Entity fetch(ID id) — достает сущность типа Entity из базы, либо из кеша по Primary Key;
- List fetchAll() — достает все сущности типа Entity, предварительно загружая их в кеш;
- void save(Entity entity) — создает/обновляет сущность типа Entity как в базе так и в кеше;
- void delete(Entity entity) — удаляет сущность типа Entity как из базы так и из кеша;
- boolean exists(ID id) — проверяет наличие сущности в базе по Primary Key.
Все CRUD-запросы происходят в транзакции.
Поддерживает автогенерацию запросов посредством переопределения функций с ключевыми словами, как в реализации выше (TblAccount findByUsernameEq(String username)) и вызов зарегистрированных запросов (NamedQuery)
Функция findByUsernameEq(String username) — осуществляет поиск сущности по ее полю username. Сгенерированный запрос:
select * from tbl_account where username = 'username'
Далее нам понадобиться, уровень для управления бизнес-логикой.
Реализации AccountService:
package org.examples.webapp.service; import org.examples.webapp.domain.entity.TblAccount; import org.examples.webapp.domain.repository.TblAccountRepository; import org.examples.webapp.responces.IMessage; import org.ioc.annotations.context.IoCComponent; import org.ioc.annotations.context.IoCDependency; import org.ioc.web.model.http.Request; import org.ioc.web.security.configuration.SecurityConfigureAdapter; import org.ioc.web.security.encoder.bcrypt.BCryptEncoder; import org.ioc.web.security.user.UserDetails; import org.ioc.web.security.user.UserDetailsProcessor; import java.util.Objects; import static org.examples.webapp.responces.IMessage.Type.ERROR; import static org.examples.webapp.responces.IMessage.Type.OK; @IoCComponent public class AccountService implements UserDetailsProcessor { @IoCDependency private TblAccountRepository tblAccountRepository; @IoCDependency private BCryptEncoder bCryptEncoder; @IoCDependency private SecurityConfigureAdapter securityConfigureAdapter; @Override public UserDetails loadUserByUsername(String username) { return tblAccountRepository.findByUsernameEq(username); } public void save(TblAccount tblAccount) { tblAccountRepository.save(tblAccount); } public void delete(TblAccount tblAccount) { tblAccountRepository.delete(tblAccount); } public IMessage tryCreateUser(String username, String password, String repeatedPassword) { if (username == null || username.isEmpty() || password == null || password.isEmpty() || repeatedPassword == null || repeatedPassword.isEmpty()) { return new IMessage(ERROR, "Invalid request parameters!"); } if (!Objects.equals(password, repeatedPassword)) { return new IMessage(ERROR, "Repeated password doesn't match!"); } final UserDetails userDetails = loadUserByUsername(username); if (userDetails != null) { return new IMessage(ERROR, "Account already exists!"); } final TblAccount account = new TblAccount(); account.setUsername(username); account.setPassword(bCryptEncoder.encode(password)); save(account); return new IMessage(OK, "Successfully created!"); } public IMessage tryAuthenticateUser(Request request, String username, String password) { if (username == null || username.isEmpty() || password == null || password.isEmpty()) { return new IMessage(ERROR, "Invalid request parameters!"); } final UserDetails userDetails = loadUserByUsername(username); if (userDetails == null) { return new IMessage(ERROR, "Account not found!"); } if (!bCryptEncoder.match(password, userDetails.getPassword())) { return new IMessage(ERROR, "Password does not match!"); } securityConfigureAdapter.getContext().authenticate(request, userDetails); return new IMessage(OK, "Successfully authenticated"); } public IMessage logout(Request request) { if (securityConfigureAdapter.getContext().removeAuthInformation(request)) { return new IMessage(OK, "/"); } return new IMessage(ERROR, "Credentials not found or not authenticated!"); } }
**Пояснения:
Аннотация @IoCComponent — служит для инициализации класса как компонента.
Аннотация @IoCDependency — служит для внедрения зависимостей в инстанс класса.
Утилита BCryptEncoder — реализация кодека BCrypt для шифрования пароля (пока что единственные кодек).
Системный инстанс SecurityConfigureAdapter — служит для работы с маппингом запросов и сессиий пользователей.
Функция UserDetails loadUserByUsername — наследуемая функция UserDetailsProcessor, служит для загрузки пользователя в сессию и выставлении флага аутентификации (в будущем для стандартного маппинга авторизации в Security)
Функция IMessage tryCreateUser — функция создания пользователя.
Функция IMessage tryAuthenticateUser — функция аутентификации пользователя.
Функция IMessage logout — функция очистки сессии от авторизированного пользователя.
Класс IMessage — класс-утилита для выведении нужной нам информации в браузере (json-ответ).
package org.examples.webapp.responces; public class IMessage { private final String message; private final Type type; public IMessage(String message) { this.message = message; type = Type.OK; } public IMessage(Type type, String message) { this.message = message; this.type = type; } public String getMessage() { return message; } public Type getType() { return type; } public enum Type { OK, ERROR } }
Теперь понадобиться реализация самой линковки (маппинга запросов):
package org.examples.webapp.mapping; import org.examples.webapp.domain.entity.TblAccount; import org.examples.webapp.responces.IMessage; import org.examples.webapp.service.AccountService; import org.ioc.annotations.context.IoCDependency; import org.ioc.annotations.web.IoCController; import org.ioc.web.annotations.Credentials; import org.ioc.web.annotations.MappingMethod; import org.ioc.web.annotations.RequestParam; import org.ioc.web.annotations.UrlMapping; import org.ioc.web.model.ModelAndView; import org.ioc.web.model.http.Request; @IoCController @UrlMapping("/") public class MainMapping { @IoCDependency private AccountService accountService; @UrlMapping public ModelAndView index() { final ModelAndView modelAndView = new ModelAndView(); modelAndView.setView("index"); return modelAndView; } @UrlMapping(value = "signup", method = MappingMethod.POST) public IMessage createUser(@RequestParam("username") String username, @RequestParam("password") String password, @RequestParam("repeatedPassword") String repeatedPassword) { return accountService.tryCreateUser(username, password, repeatedPassword); } @UrlMapping(value = "signin", method = MappingMethod.POST) public IMessage auth(Request request, @RequestParam("username") String username, @RequestParam("password") String password) { return accountService.tryAuthenticateUser(request, username, password); } @UrlMapping("signout") public IMessage signout(Request request) { return accountService.logout(request); } @UrlMapping("loginPage") public ModelAndView authenticated(@Credentials TblAccount account) { final ModelAndView modelAndView = new ModelAndView(); modelAndView.setView("auth"); modelAndView.addAttribute("account", account); return modelAndView; } }
**Пояснения:
Аннотация @IoCController — служит для идентификации класса в контексте, как контроллера (маппера запросов браузера)
Аннотация @UrlMapping — указывает, что нужно проанализировать функцию/класс на наличие запросов, обрабатываемых хендлерами канала.
Параметры:
- value — нужный нам запрос;
- method — http метод для обработки (GET, POST, PUT, etc.);
- consumes — http mime type для проверки наличия запроса конкретного типа (опционально);
- produces — http content-type для отдачи в ответе конкретного типа контента (Content-Type: text/html; charset=utf-8, Content-Type: multipart/form-data; boundary=something, etc. опционально;
Аннотация @RequestParam — служит для определении имени получаемого параметра из запроса. Поскольку постольку дефолтными средствами рефлексии нельзя получить текущее имя параметра метода, мне было лень подключать лишнюю зависимость javaassist, шаманить с асмом. Поэтому такой себе метод определения имени параметра для внедрения этому параметру значения, полученного из запроса. Существует аналог для GET типа — @PathVariable — тот же самый принцип работы (не совместим с POST).
Аннотация @Credentials — служит для вставки текущих данных авторизированного пользователя, в противном случаи может быть null, если информации авторизированного пользователя нет в сесии.
Системный класс Request — текущая информация о поступившем запросе, содержащая в себе коки, хидеры и канал пользователя, который в последствии можно будет отправлять Push Message's… у кого какая фантазия уже на этот счет.
Класс-утилита ModelAndView — модель страницы с именем ресурса без расширения, и атрибутами для внедрения в ресурс.
Вроде бы все, но нет — нужно обязательно сконфигурировать доступный маппинг запросов для пользователей.
package org.examples.webapp.config; import org.ioc.annotations.configuration.Property; import org.ioc.annotations.configuration.PropertyFunction; import org.ioc.web.security.configuration.HttpContainer; import org.ioc.web.security.configuration.SecurityConfigureProcessor; import org.ioc.web.security.encoder.Encoder; import org.ioc.web.security.encoder.bcrypt.BCryptEncoder; import org.ioc.web.security.filter.CorsFilter; import org.ioc.web.security.filter.CsrfFilter; @Property public class SecurityConfig implements SecurityConfigureProcessor { @Override public void configure(HttpContainer httpContainer) { httpContainer. configureRequests(). anonymousRequests("/", "/signup", "/signin"). resourceRequests("/static/**"). authorizeRequests("/loginPage", "ROLE_USER"). authorizeRequests("/signout", "ROLE_USER"). and(). configureSession(). expiredPath("/"); } @PropertyFunction public CsrfFilter csrfFilter() { return new CsrfFilter(); } @PropertyFunction public CorsFilter corsFilter() { return new CorsFilter(); } @PropertyFunction public Encoder encoder() { return new BCryptEncoder(); } }
**Пояснения:
Аннотация @Property — сообщает контексту, что это конфигурационный файл и его нужно инициализировать.
Аннотация @PropertyFunction — сообщает анализатору конфигураций, что эта функция возвращает какой-то тип и должен его инициализировать как компонент (бин).
Интерфейс SecurityConfigureProcessor — утилита служащая для конфигурации маппинга запросов.
Класс-модель HttpContainer — утилита, хранящая в себе маппинг запросов, указанных пользователем.
Класс CsrfFilter — фильтр не валидных запросов (реализации механики CSRF).
Класс CorsFilter — фильтр Cross-Origin Resource Sharing.
Функция anonymousRequests — принимает в себя неограниченный массив запросов, не требует авторизированных пользователей и проверки ролей (ROLE_ANONYMOUS).
Функция resourceRequests — принимает в себя неограниченный массив запросов, конкретно служит для определения, по какому пути будет лежать ресурсный файл, не требующей сложной обработки (css, js, images, etc.).
Функция authorizeRequests — принимает в себя неограниченный массив запросов, требует авторизированного пользователя и конкретную роль, присущую пользователю.
Функция expiredPath — при очистке истекшей по времени сессии, пользователя перебрасывает по этому маппингу (redirect link).
Что ж, остались страницы, скрипты и стили сайта (глубоко углубляться не буду).
index.vm — главная страница
<html> <head> <meta charset="utf-8"/> <title>IoC Test</title> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/style.css"/> <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/> <link rel="stylesheet" href="/static/css/pnotify.css"/> <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/> </head> <body> <div class="container"> <h1>IoC Starter Test</h1> <br> <h4>Create user</h4> <br> <form id="creation"> <label for="username">Username: </label> <input type="text" id="username" name="username" class="color-input-field"/> <label for="password">Password: </label> <input type="password" id="password" name="password" class="color-input-field"/> <label for="repeatedPassword">Repeate: </label> <input type="password" id="repeatedPassword" name="repeatedPassword" class="color-input-field"/> <button type="button" class="btn btn-success btn-create">Sing up!</button> </form> <h4>Authenticate</h4> <br> <form id="auth"> <label for="username">Username: </label> <input type="text" id="username" name="username" class="color-input-field"/> <label for="password">Password: </label> <input type="password" id="password" name="password" class="color-input-field"/> <button type="button" class="btn btn-danger btn-auth">Sing in!</button> </form> </div> <script type="text/javascript" src="/static/js/jquery.js"></script> <script type="text/javascript" src="/static/js/bootstrap.min.js"></script> <script type="text/javascript" src="/static/js/scripts.js"></script> <script type="text/javascript" src="/static/js/pnotify.js"></script> <script type="text/javascript" src="/static/js/pnotify.buttons.js"></script> </body> </html>
auth.vm — для отображения авторизированного пользователя
<html> <head> <meta charset="utf-8"/> <title>IoC Test</title> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/style.css"/> <link rel="stylesheet" href="/static/css/pnotify.custom.min.css"/> <link rel="stylesheet" href="/static/css/pnotify.css"/> <link rel="stylesheet" href="/static/css/pnotify.buttons.css"/> </head> <body> <div class="container"> <h1>Authorized page</h1> <br> <h4>Test auth data</h4> <div id="auth_data"> #if($!account) <h4>Hello @$!account.username, You successfully authenticated!</h4> <br> <button type="button" class="btn btn-success btn-logout">Logout!</button> #end </div> </div> <script type="text/javascript" src="/static/js/jquery.js"></script> <script type="text/javascript" src="/static/js/bootstrap.min.js"></script> <script type="text/javascript" src="/static/js/scripts.js"></script> <script type="text/javascript" src="/static/js/pnotify.js"></script> <script type="text/javascript" src="/static/js/pnotify.buttons.js"></script> </body> </html>
scripts.js — контроллер, для отправки и получения информации запроса на сервер
$(function () { $(".btn-create").click(function () { var cooki = cookie(); document.cookie = 'CSRF-TOKEN=' + cooki; $.ajax({ url: "/signup", data: $('#creation').serialize(), headers: {'X-CSRF-TOKEN': cooki}, crossDomain: true, xhrFields: { withCredentials: true }, type: "POST" }).done(function (data) { switch (data.type) { case 'OK': new PNotify({ title: 'Success', text: data.message, type: 'success', hide: false }); break; case 'ERROR': new PNotify({ title: 'Error', text: data.message, type: 'error', hide: false }); break; } }); }); $(".btn-auth").click(function () { var cooki = cookie(); document.cookie = 'CSRF-TOKEN=' + cooki; $.ajax({ url: "/signin", data: $('#auth').serialize(), headers: {'X-CSRF-TOKEN': cooki}, crossDomain: true, xhrFields: { withCredentials: true }, type: "POST" }).done(function (data) { switch (data.type) { case 'OK': new PNotify({ title: 'Success', text: data.message, type: 'success', hide: false }); setTimeout(function () { window.location = "/loginPage"; }, 5000); break; case 'ERROR': new PNotify({ title: 'Error', text: data.message, type: 'error', hide: false }); break; } }); }); $(".btn-logout").click(function () { $.ajax({ url: "/signout", crossDomain: true, xhrFields: { withCredentials: true }, type: "GET" }).done(function (data) { switch (data.type) { case 'OK': new PNotify({ title: 'Success', text: 'Logouting...', type: 'success', hide: false }); setTimeout(function () { window.location = data.message; }, 5000); break; case 'ERROR': new PNotify({ title: 'Error', text: data.message, type: 'error', hide: false }); break; } }); }); }); function cookie(a) { return a // if the placeholder was passed, return ? ( // a random number from 0 to 15 a ^ // unless b is 8, Math.random() // in which case * 16 // a random number from >> a / 4 // 8 to 11 ).toString(16) // in hexadecimal : ( // or otherwise a concatenated string: [1e7] + // 10000000 + -1e3 + // -1000 + -4e3 + // -4000 + -8e3 + // -80000000 + -1e11 // -100000000000, ).replace( // replacing /[018]/g, // zeroes, ones, and eights with cookie // random hex digits ) }
Компилим, запускаем все что у нас получилось.
Если все правильно, увидим что-то подобное в конце загрузки:
[21.10.18 22:29:51:990] INFO web.model.mapping.MappingContainer: Mapped method [/], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.index()]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signup], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.createUser(java.lang.String,java.lang.String,java.lang.String)]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signin], method=[POST], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.auth(org.ioc.web.model.http.Request,java.lang.String,java.lang.String)]
[21.10.18 22:29:51:993] INFO web.model.mapping.MappingContainer: Mapped method [/signout], method=[GET], to [public org.examples.webapp.responces.IMessage org.examples.webapp.mapping.MainMapping.signout(org.ioc.web.model.http.Request)]
[21.10.18 22:29:51:995] INFO web.model.mapping.MappingContainer: Mapped method [/loginPage], method=[GET], to [public org.ioc.web.model.ModelAndView org.examples.webapp.mapping.MainMapping.authenticated(org.examples.webapp.domain.entity.TblAccount)]
[21.10.18 22:29:51:997] INFO ioc.web.factory.HttpInitializerFactory: Http server started on port(s): 8081 (http)
Результат:
1) Главная страница

2) Регистрация

3) Аутентфикация

4) Страница с результатом авторизации(редирект после правильного ввода логина и пароля)

5) Очистка информации авторизации из сесии и перенаправления пользователя на стартовую страницу

6) Попытка не авторизированного пользователя попасть на страницу с информацией аутентификации сессии

Конец
Проект развивающийся, приветствуются "контрибьюторы" и оригинальные идеи, поскольку одному делать этот проект тяжеловато.
Репозиторий проекта.
Контекст
ОРМ фабрика
Веб фабрика
Примеры
Текущий пример из статьи
Так же на репозитории имеются примеры использования всего функционала в модуле 'examples', и как говориться, "Good luck, have fun", всем спасибо за внимание.
