Всем доброго времени суток.
Эта статья рассчитана для новичков в мире Web и Java. Мы рассмотрим стандартную архитектуру веб-приложения и сделаем небольшой проект с нуля.
Будем использовать Spring (Огромный фреймворк для веба), но по минимуму. Если не иметь предыдущего опыта использования и сразу пытаться использовать весь функционал, то не будет базового понимания, так как имеется перечень подводных камней.
Что будет делать приложение
Хочу представить короткий и в то же время полезный пример. Он минимально загружен и по итогу вы сможете использовать его как шаблон.
Думая, с чем люди часто должны сталкиваться при разработки веб-приложения, понял что реализация логина и регистрации будет достаточно показательной и полезной.
Исходный код вы сможете найти здесь.
Что будем использовать
- любимую IDE (я использую IntelliJ IDEA)
- не замутненную голову
- Maven (сборщик проекта)
- JDBC (звено между Java и СУБД)
Создание проекта
Создайте простой Maven проект. Делаем все с чистого листа, не добавляя никаких архетипов.
IDE должна сгенерировать такую структуру.
И так, что мы видим.
- java — основное место где будет происходить "магия"
- resources — место где хранятся наши ресурсы. (Это могут быть файлы, картинки и чего только душа пожелает, зачастую тут хранится front часть приложения)
- test — место где принято писать тесты к приложению. (Вы можете с чистой совестью удалять эту папку, тестирование мы рассматривать не будем)
- pom.xml — сердце Maven. В нем содержится информация о проекте, тут прописываются все зависимости, указывается какой тип "упаковки" должен быть у проекта и тд.
Кстати, не пропустите сообщения от IDE, и разрешите авто-импорт для Maven.
Архитектура веб-приложения
Чтобы начать кодить, нужно подкрепиться немного теорией.
Простите за мой дизайнерский талант, я в этом не силен.
На картинке показаны слои веб-приложения, каждый слой может взаимодействовать только со своими соседями, если приложение написано "по-правильному".
И так пробежимся по каждому слою и разберем их назначения.
- Browser — это лицо нашего приложения. То что видит пользователь и что называют Frontend. Он может взаимодействовать с Backend — серверной частью, а именно с контроллером. Этот слой должен уметь отправлять и получать данные от контроллера.
- Controller — должен контролировать запросы от Frontend, уметь их обрабатывать и возвращать данные. И, конечно же, задачей контролера является общение со следующим слоем.
- Service — в этом слое должна быть реализована бизнес-логика приложения. Можно считать этот слой корнем приложения. В нем происходят самые тяжеловесные операции.
- DAL(data access layer) — этот слой должен общаться с нашей БД. В нём реализованы все запросы к БД, которые нужны для приложения, а именно запросы на выборку, изменения и тд.
- Database — она же БД, структурированная туса данных.
Проектирование базы данных
Один из самых первых этапов разработки веб-приложения является проектирование БД.
Выбор БД предоставляется на ваше усмотрение, но у меня в примере будет использоваться H2.
Так как я хочу чтобы пример вышел небольшой, но чтобы в нем удалось рассмотреть как можно больше скользких моментов и понять всеобщие принципы построения веб-приложений. В примере будет только одна таблица User.
create table DATABASE.USER ( ID INT auto_increment, NAME VARCHAR not null, SURNAME VARCHAR not null, LOGIN VARCHAR not null, PASSWORD VARCHAR not null, constraint USER_PK primary key (ID) );
Добавляем Spring в проект
Вспоминаем про нашу папку java.
Создаем внутри java пакет com.zuk(вы можете называть иначе), в нем класс App с обычной main функцией.
И добавим щепотку магии Spring. Не будем вдаваться сильно в то что здесь происходит, с помощью @SpringBootApplication мы просто говорим Spring, чтобы он делал автоматическую настройку, чтобы он сканировал все что ему нужно. Сейчас рекомендую рассматривать Spring как автономную республику.
//com.zuk.App @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
Не впадаем в панику если все красное и не забываем подключать зависимости в pom.xml.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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> <groupId>org.zuk</groupId> <artifactId>LoginAndRegistration</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.2.5.RELEASE</version> </dependency> </dependencies> </project>
Если вы все сделали правильно у вас появится возможность сделать билд проекта, и Spring скажет вам привет.
Spring говорит что он стартанул сервер на порту 8080. Мы можем сами убедиться в этом, сходив на http://localhost:8080.
Подключаем БД к проекту
И так мы уже создали нашу Базу даных, подключили Spring к проекту, но они ещё не знают о существование друг друга.
Давайте создадим application.properties по пути src.main.resources.
Этот файл служит для хранения конфигурационных параметров в формате ключ-значение.
spring.datasource.driverClassName=org.h2.Driver spring.datasource.url=jdbc:h2:~/test spring.datasource.username=sa spring.datasource.password=
Это стандартные ключи для Spring, и он сам может их прочитать.
Чтобы вы смогли убедиться в соединении, предлагаю проверить сможем ли мы подключиться в БД.
Хочу предупредить, моё решение будет спорным. На мой взгляд для новичка оно будет показательным и не будет вызывать много вопросов, но не стоит сильно опираться на данный подход. В Spring есть интересные реализации для работы с БД.
Советую почитать несколько статей по данной тематике.
По пути java.com.zuk.connection создадим класс ConnectionManager, он должен отдавать нам соединение с БД, и с этим соединением мы в дальнейшем будем работать.
Для этого нам понадобится вытянуть наши properties, сделаем это с помощью FileInputStream.
//ConnectionManager FileInputStream fis; Properties property = new Properties();
Проберемся к нашему application.properties, и вытянем с него properties.
//ConnectionManager fis = new FileInputStream("src/main/resources/application.properties"); property.load(fis);
Создаем метод getConnection.
//ConnectionManager public Connection getConnection() { Connection connection = null; try { connection = DriverManager.getConnection(property.getProperty("spring.datasource.url"),property.getProperty("spring.datasource.username"),property.getProperty("spring.datasource.password")); } catch (SQLException e) { e.printStackTrace(); } return connection; }
Теперь можете проверить есть ли соединение, сделать это можно и в App.
Если у вас все получилось, должно вывести такое.
conn2: url=jdbc:h2:~/test user=SA
Ах да, не забываем добавить зависимость в наш любимый pom.
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.200</version> </dependency>
POJO (Plain Old Java Objects)
Не пугайтесь странных аббревиатур, я расскажу простыми словами зачем это нам надо, и что мы с этим будем делать.
Представьте простой класс, который ничего не наследует, в который не должно быть реализованы ничего сложно. Это просто класс, который служит носителем информации, в котором есть некоторые поля, геттеры и сеттеры для них. Максимум, что может быть, это реализация стандартных методов объекта. Это и есть наш POJO.
Для нашего проекта, это будет описание сущности таблицы User.
То есть мы реализуем таблицу в виде класса.
И так создаем пакет java.com.zuk.entity, в нем класс User.
Запишем в поля класса поля таблицы, и реализуем для каждого геттер и сеттер.
public class User { int id; String name; String surname; String login; String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
DAL
Что же, самое время приступить к следующему слою, а именно DAL. Тут будут реализованы все запросы к нашей БД, которые нам понадобятся. Честь этого слоя будет отстаивать DAO (data access object) — интерфейс к какому-либо типу базы данных или механизму хранения.
Создаем пакет java.com.zuk.dao в нем интерфейс UserDao (принято сначала писать имя таблицы). Опишем пока что только два метода findByLogin, save.
public interface UserDao { User findByLogin(User user); Boolean save(User user); }
Дальше напишем реализацию для этого интерфейса, создаем пакет java.com.zuk.dao.impl,
в нем класс UserDaoImpl.
Вы можете спросить зачем так сильно разделять все — это будет служить хорошей привычкой, вдруг вам придется работать в большом проекте, там скорее всего будут специфические реализации (например, если используется несколько БД).
public class UserDaoImpl implements UserDao { @Override public User findByLogin(User user) { return null; } @Override public Boolean save(User user) { return null; } }
Начнем с findByLogin. Нам нужно сделать такой SQL запрос.
SELECT * from DATABASE.User where LOGIN=login
Далее нужно вернуть объект класса User.
Проверим есть ли у нас соединение с базой, если же его не будет мы вернем null.
//UserDaoImpl ConnectionManager cm = new ConnectionManager(); Connection con = cm.getConnection(); @Override public User findByLogin(String login) { User user = null; if (con != null) { //.. } return user; }
Если условие в if правильное, мы выполним SQL запрос, и запишем значение в user.
//findByLogin PreparedStatement pr = con.prepareStatement("SELECT * FROM DATABASE.User where LOGIN=?"); pr.setString(1 , login); ResultSet resultSet = pr.executeQuery();//return sql result if(resultSet.next()) { user = new User(); user.setId(resultSet.getInt("ID")); user.setName(resultSet.getString("NAME")); user.setSurname(resultSet.getString("SURNAME")); user.setLogin(login); user.setPassword(resultSet.getString("PASSWORD")); return user; } pr.close(); con.close();
Не забываем покрывать возможные исключения.
Попробуйте реализовать save своими руками.
Код оставлю под спойлером.
//UserDaoImpl @Override public Boolean save(User user) { ConnectionManager cm = new ConnectionManager(); Connection con = cm.getConnection(); if (con != null) { try { PreparedStatement pr = con.prepareStatement("insert into DATABASE.USER (NAME,SURNAME,LOGIN,PASSWORD) values (?,?,?,?)"); pr.setString(1,user.getName()); pr.setString(2,user.getSurname()); pr.setString(3,user.getLogin()); pr.setString(4, DigestUtils.md5DigestAsHex((user.getPassword()).getBytes())); pr.executeUpdate(); pr.close(); con.close(); return true; } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } return false; }
Service
В сервисе будет находится наша бизнес-логика. Создаем пакет java.com.zuk.service, в нем интерфейс UserService с двумя методами: login, registration. И создаем класс UserServiceImpl в java.com.zuk.service.impl, который имплементирует UserService.
public class UserServiceImpl implements UserService { @Override public String login(User user) { return null; } @Override public String registration(User user) { return null; } }
Предлагаю начать с login.
Алгоритм будет таким:
- Проверим существует ли поле "login" в БД.
- Если условие 1 выполняется, проверяем совпадает ли пароль.
- Если условие 2 выполняется, скажем "life is beautiful"
//UserServiceImpl UserDaoImpl userDao = new UserDaoImpl(); @Override public String login(User user) { User findUser = userDao.findByLogin(user.getLogin()); if(findUser!=null){ if(DigestUtils.md5DigestAsHex((user.getPassword()).getBytes()).equals(findUser.getPassword())){ return "life is beautiful"; } } return "do not give up"; }
Попытайтесь сделать регистрацию своими руками.
@Override public String registration(User user) { User findUser = userDao.findByLogin(user.getLogin()); if(findUser==null) { userDao.save(user); return "life is beautiful"; } return "this login is not available"; }
Controller
Настала звездный час контроллера, и мы наконец-то сможем увидеть результат наших деяний.
Создаём пакет java.com.zuk.controller в нем класс Controller. Перед классом разместим "магическую" аннотацию RestController, она поможет Spring понять, что этот класс будет служить нам как Rest Controller.
Создаём метод, который возвращает строку и аннотируем его с помощью @RequestMapping("/") и @ResponseBody.
@RestController public class Controller { @RequestMapping("/") @ResponseBody String main() { return "Hello from Controller"; } }
Делаем билд проекта, и переходим на http://localhost:8080.
Мы должны увидеть "Hello from Controller".
Теперь создаём два html файла. Один будет с формой регистрации, второй с формой логина.
Правильным тоном будет сохранение этих файлы внутри resources, а еще лучше в папку resources.static.
<!--loginFrom.html--> <html> <title>Login</title> <body> <form action="http://localhost:8080/login"> <input type="text" required name="login" placeholder="login" /> <input type="password" required name="password" placeholder="password" /> <button>login</button> </form> <a href="http://localhost:8080/registrationForm">don't have account</a> </body> </html>
<!--registrationForm.html--> <html> <title>Registration</title> <body> <form action="http://localhost:8080/registration"> <input type="text" required name="name" placeholder="name" /> <input type="text" required name="surname" placeholder="surname" /> <input type="text" required name="login" placeholder="login" /> <input type="password" required name="password" placeholder="password" /> <button>registration</button> </form> <a href="http://localhost:8080/loginForm">already have account</a> </body> </html>
Теперь я хочу, чтобы при переходе по ссылке http://localhost:8080/loginForm, или http://localhost:8080/registrationForm выводились наши html файлы с формами.
Возвращаемся к нашему контроллеру, тут мы должны добавить мапинг для /loginForm, /registrationForm и отдавать соответствующую html. В этом нам поможет ModelAndView.
//Controller @RequestMapping("/loginForm") @ResponseBody ModelAndView loginForm() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("loginForm.html"); return modelAndView; } @RequestMapping("/registrationForm") @ResponseBody ModelAndView registrationForm() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("registrationForm.html"); return modelAndView; }
Если вы заметили, то в форме я уже написал action на http://localhost:8080/login и registration, но мы ещё не ловим эти переходы в контроллере.
Пора это исправлять и связаться с нашим service.
//Controller UserServiceImpl userService = new UserServiceImpl(); //.. @RequestMapping("/login") @ResponseBody String login(@RequestParam String login,@RequestParam String password) { User user = new User(); user.setLogin(login); user.setPassword(password); return userService.login(user); } @RequestMapping("/registration") @ResponseBody String registration(@RequestParam String name,@RequestParam String surname, @RequestParam String login,@RequestParam String password) { User user = new User(); user.setName(name); user.setSurname(surname); user.setLogin(login); user.setPassword(password); return userService.registration(user); }
Мы видим, что наши методы в Controller начали принимать параметры. Это те параметры, которые мы пишем в наших формах. Их имена должны совпадать с именами input.
Заключение
Надеюсь эта статья смогла ответь на некоторые ваши вопросы, и помогла расширить свои горизонты.
Всем приятного развития.
ссылка на оригинал статьи https://habr.com/ru/post/495010/
Добавить комментарий