Содержание:
- Введение
- Создание проекта
- Подключение Spring MVC
- Создание страниц и контроллера
- Конфигурация
- Модель
- Model-View-Controller
Введение
Знакомство с новыми для меня технологиями и фреймворками я начинал с изучения различных примеров, в которых они использовались, потому что обычно я лучше всего что-то понимаю, когда вижу работу на примере полноценного приложения. Обычно в качестве таких примеров выступают CRUD-приложения (Create, Read, Update, Delete), в интернете полно таких примеров разной степени сложности. Проблема в том, что там обычно не объясняют подробно как, что и зачем там сделано, почему добавлена такая-то зависимость, почему нужен именно такой класс и т.д. В большинстве случаев берут уже полностью готовое приложение, с итоговым POM файлом, с конечными вариантами классов и просто пробегают по каждому, не заостряя внимания на мелочах, которые для опытного человека, наверное, кажутся очевидными. Я много таких примеров разобрал и обычно вроде как и понятно, как там все работает, но вот как к этому пришли не совсем ясно. Поэтому я решил будет не лишним такой пример не с позиции опытного разработчика, а с позиции новичка, который ни разу не имел дело со Spring, Hibernate и прочими вещами.Создание проекта
Итак, поскольку я новичек, то использовать какие-то непонятные архетипы не будем. Spring initializr так и вовсе пока звучит слишком страшно. Поэтому создадим самый обычный простой Maven проект. У меня нет доменного имени, так что в groupid напишу простоtestgroup
, а в artifactid напишу название, ну например, filmography
(это будет список фильмов). Создаем проект и выбираем Enable auto-import
когда идея это предложит. Благодаря этому каждый раз, когда мы будем вносить какие-либо изменения в POM файл (Project Object Model, в этом файле описывается вся структура Maven проекта) все сразу же автоматически будем применяться к проекту.
Библиотеки будут браться из нашего локального репозитория, если они уже имеются у нас в наличии, либо, если будем использовать какие-то новые зависимости, с которыми раньше дел не имели, то Maven просто скачает их через интернет из центрального репозитория. Еще у Maven есть функция загружать исходники и документацию (Download Sources and/or Documentation). Тоже очень удобно, если что-то не понятно с каким-то классом или методом, можно зайти в исходники и посмотреть, как оно там все внутри устроено.
Добавим пару деталей. Это будет веб-приложение, и мы будем использовать Tomcat. Для развертывания (deploy) приложения в Tomcat нужно передать его туда в виде war архива (Web Application Resource, специальный формат для веб-приложений). Для этого добавим в POM файл такую строчку, чтобы приложение собиралось в war архив:
<packaging>war</packaging>
Ну и еще понадобится специальная директория для веб-исходников, в нашем случае там будут лежать jsp страницы, какие-то веб-ресурсы. Создадим в main
директорию webapp
. Она должна называться именно так и находиться именно в main
, так же как java
и resources
, потому что это стандартная структура каталогов Maven. Когда мы установили упаковку в war
и тем самым определили, что это веб-проект, директория webapp
будет автоматически помечена как Web application sources (на ней будет голубая точка) и все, что касается веб, будет искаться в этой папке.
И еще один момент. По-умолчанию Maven использует версию языка 1.5, но я хочу использовать, например, версию 1.8 – Java 8 (Можно и 10 взять, или 11, но все равно никакие фишки оттуда использовать не планируется, так что пускай 8 будет). Решается это очень просто, пишем в гугле что-то вроде "Maven java 8" и смотрим что нужно добавить в POM файл, чтобы Maven компилировал наши классы для нужной версии.
В итоге имеем следующее:
Подключение Spring MVC
Нужно с чего-то начать. По плану мы будем подключать базу данных, использовать Hibernate, но это все пока звучит как-то слишком страшно. Нужно сперва заняться чем-нибудь попроще. Spring MVC, это уже получше, с MVC паттерном уже давным-давно знакомы, он использовался в половине больших задач курса. Отсюда и начнем плясать. Для создания веб приложения со Spring MVC нам также понадобится Servlet-API, т.е. та штука, с помощью которой будет происходить взаимодействие запрос-ответ. Попробуем подключить это. Заходим в гугл, ищем нужные зависимости в репозитории Maven и добавляем их вpom.xml
.
В разделе External Libraries видно, что подгрузилась не только spring-webmvc, но и куча чего еще. Т.е. нам не нужно дополнительно подключать зависимости для spring core, context, beans и т.д. которые нам понадобятся, все необходимое подтянулось само вместе со spring-webmvc.
Нужно сделать небольшую оговорку. Обычно рекомендуется все равно отдельно добавлять зависимость для каждой используемой библиотеки, даже если они и так идут в комплекте с уже добавленными, т.к. это может помочь избежать некоторых проблем и глюков. Простой пример. Допустим мы добавили зависимость, которая использует какое-то API, и заодно она подтянет для этого API какую-то реализацию. А потом мы добавили другую зависимость, которая использует тоже самое API и тоже подтягивает для этого какую-то его реализацию, но уже другую. Таким образом у нас в наличие будет 2 разные реализации одного и того же API. И если мы сами захотим где-то использовать какие-то методы этого API, тогда и возникнет проблема, ведь система не будет знать, какую именно реализацию нужно использовать, она выберет случайно, возможно не ту, что мы ожидали. А если явно указать зависимость для одной из реализаций, то приоритет будет отдан именно ей. Однако это не такая строгая рекомендация, это касается в основном крупных проектов, где используется множество рзличных библиотек от разных компаний. Тут мы так делать не будем, дабы не загружать слишком сильно POM файл, каких-то проблем не предвидится. Но тем не менее все же стоит иметь это ввиду.
|
provided
в зависимости javax.servlet-api
. Scope — это область действия зависимости, provided
означает что зависимость будет доступна на этапе компиляции и тестирования приложения, но в архив она помещена не будет. Дело в том, что для развертывания (deploy) приложения мы будем использовать контейнер сервлетов, Tomcat, а у него внутри уже есть такие библиотеки, поэтому нет нужды передавать их туда и утяжелять архив лишним грузом. Забегая вперед, по той же причине мы обойдемся без привычного метода main
, потому что он уже есть внутри Tomcat.
Создание страниц и контроллера
Попробуем теперь состряпать что-нибудь простенькое. Для начала создадим вwebapp
дополнительную директорию, например pages
, в которой будут храниться наши представления (view), т.е. jsp-страницы, и создадим пару страниц. Нам понадобится страница, на которой в будущем будет отображаться список фильмов, допустим films.jsp
, и еще, пожалуй, можно сделать отдельную страницу для редактирования, пускай это будет editPage.jsp
. Пока ничем серьезным их заполнять не будем, просто для пробы сделаем на одной странице ссылку на другую.
Теперь нужен класс, который будет обрабатывать запросы, т.е. контроллер. Добавим новый пакет controller
и создадим в нем класс FilmController
(вообще не обязательно расфасовывать все по разным пакетам, это приложение будет очень маленькое и простое, но в нормальном проекте может быть много контроллеров, классов конфигурации, моделей и т.д., поэтому даже начиная с маленьких проектов лучше сразу привыкать делать все упорядоченно и структурировано, чтобы не было каши). В этом классе создадим методы, которые будут возвращать наши представления в ответ на запросы.
package testgroup.filmography.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class FilmController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView allFilms() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("films");
return modelAndView;
}
@RequestMapping(value = "/edit", method = RequestMethod.GET)
public ModelAndView editPage() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("editPage");
return modelAndView;
}
}
Что тут к чему. У Spring MVC есть такая штука, как DispatcherServlet
. Это как бы главный контроллер, все входящие запросы проходят через него и он уже дальше передает их конкретному контроллеру. Аннотация @Controller
как раз и сообщает Spring MVC, что данный класс является контроллером (ну логично в общем-то), диспетчер будет проверять аннотации @RequestMapping
чтобы вызвать подходящий метод. Аннотация @RequestMapping
позволяет задать адреса методам контроллера, по которым они будут доступны в клиенте (браузер). Ее можно применять также и к классу контроллера, чтобы задать, так сказать, корневой адрес для всех методов.
Для метода allFilms()
параметр value
установлен "/
", поэтому он будет вызван сразу, когда в браузере будет набрана комбинация http://host:port/ (т.е. по-умолчанию это http://localhost:8080/ или http://127.0.0.1:8080/). Параметр method
указывает кокой тип запроса поддерживается (GET, POST, PUT и т.д.). Поскольку тут мы только получаем данные то используется GET. Позднее, когда появятся методы для добавления и редактирования там уже будут POST запросы. (Кстати вместо аннотации @RequestMapping
с указанием метода, можно использовать аннотации @GetMapping
, @PostMapping
и т.д. @GetMapping
эквивалентно @RequestMapping(method = RequestMethod.GET
)). В наших методах создаем объект ModelAndView
и устанавливаем имя представления, которое нужно вернуть.
Конфигурация
Перейдем к настройке конфигурации. Создадим в пакетеconfig
класс WebConfig
. В нем будет только один метод возвращающий объект типа ViewResolver
, это такой интерфейс, необходимый для нахождения представления по имени.
package testgroup.filmography.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "testgroup.filmography")
public class WebConfig {
@Bean
ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
@Configuration
сообщает Spring что данный класс является конфигурационным и содержит определения и зависимости bean
-компонентов. Бины (bean) — это объекты, которые управляются Spring'ом. Для определения бина используется аннотация @Bean
.
@EnableWebMvc
позволяет импортировать конфигурацию Spring MVC из класса WebMvcConfigurationSupport
. Можно также реализовать, например, интерфейс WebMvcConfigurer
, у которого есть целая куча методов, и настроить все по своему вкусу, но нам незачем пока в это углубляться, хватит и стандартных настроек.
@ComponentScan
сообщает Spring где искать компоненты, которыми он должен управлять, т.е. классы, помеченные аннотацией @Component
или ее производными, такими как @Controller
, @Repository
, @Service
. Эти аннотации автоматически определяют бин класса.
В методе viewResolver()
мы создаем его реализацию и определяем где именно искать представления в webapp
. Поэтому когда в методе контроллера мы устанавливали имя "films
" представление найдется как "/pages/films.jsp
"
Итак, класс конфигурации у нас есть, но пока что это просто какой-то отдельный класс, он сам по себе и на наше приложение никак не влияет. Нам нужно зарегистрировать эту конфигурацию в контексте Spring. Для этого нужен класс AbstractAnnotationConfigDispatcherServletInitializer
. В пакете config
создаем его наследника, допустим AppInitializer, и реализуем его методы.
package testgroup.filmography.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
В последнем методе регистрируются адреса и еще есть 2 метода для регистрации классов конфигурации. Веб-конфигурации, где определяются ViewResolver
'ы и тому подобное, помещаем в getServletConfigClasses()
. Обо всем этом лучше почитать в документации и разных гайдах, но в нашем случае не обязательно пока в это углубляться, наш WebConfig
в принципе можно и в RootClasses
определить, можно даже и в оба сразу, все равно будет работать.
Еще один момент. Возможно будут проблемы с кодировкой, когда при отправке с формы значений с русскими символами в результате будут получаться каракули. Для решения этой проблемы добавим фильтр, который будет заниматься предварительной обработкой запросов. Заходим в класс AppInitializer и переопределяем метод getServletFilters
, в котором укажем нужную кодировку, она разумеется должна быть такой же как и везде, как на страницах и в базе данных:
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[] {characterEncodingFilter};
}
Ну вроде все настроили, можно попробовать запустить и посмотреть, что получилось.
Run -> Run -> Edit Configurations -> Add New Configuration -> Tomcat Server -> Local
Далее нужно выбрать артефакт для развертывания. Идея сама даст подсказку Warning: No artifacts marked for deployment. Жмем кнопку fix и выбираем ...: war exploded. Либо можно зайти в Deployment -> add -> Artifact -> ...: war exploded.
И еще нужно зайти в Deployment и установить в поле Applecation context (это будет частью url адреса, по которому приложение будет доступно в браузере) значение "/
".
Тогда наше приложение будет сразу доступно по адресу http://localhost:8080/ (но также можно указать там что-то, ну например "/filmography
", и тогда просто нужно будет добавлять это ко всем адресам, т.е. например будет не "http://localhost:8080/edit", а будет "http://localhost:8080/filmography/edit").
Нажимаем Run и ждем пока запустится. Вот что у меня получилось:
Вроде бы все хорошо, но есть тут один нюанс. Дело в том, что наши страницы сейчас общедоступны и доступ к ним можно получить напрямую, написав путь в адресной строке. Вводим http://localhost:8080/pages/films.jsp и вот мы без ведома контроллера получили нашу страницу. Как-то это не очень правильно, поэтому мы создадим в webapp
специальную директорию WEB-INF
. То, что внутри, будет скрыто для публики и получить доступ можно будет только через контроллер. Помещаем директорию с нашими представлениями (pages
) в WEB-INF
, а в ViewResolver
соответственно добавляем ее в префикс:
viewResolver.setPrefix("/WEB-INF/pages/");
Теперь по адресу http://localhost:8080 получаем нашу страницу, а если попытаться напрямую http://localhost:8080/WEB-INF/pages/films.jsp то получим ошибку 404.
Ну вот, замечательно, у нас получилось простейшее веб-приложение, Hello World что называется. Структура проекта на данный момент выглядит так:
Модель
У нас уже есть представления и контроллер, но в MVC есть еще и 3-я буква, поэтому для полноты картины добавим еще и модель. В пакетеmodel
создадим класс Film
ну, например, c такими полями: int id
, String title
(название), int year
(год выхода), String genre
(жанр) и boolean watched
(т.е. смотрели уже этот фильм или нет).
package testgroup.filmography.model;
public class Film {
private int id;
private String title;
private int year;
private String genre;
private boolean watched;
// + Getters and setters
}
Ничего особенного, самый обыкновенный класс, приватные поля, геттеры и сеттеры. Объекты таких классов еще называют POJO
(Plain Old Java Object), ну т.е. "простой джава объект".
Попробуем теперь создать такой объект и вывести его на странице. Пока не будем особо париться как его создавать, инициализировать. Для пробы просто тупо создадим его прямо в контроллере, например, вот так:
public class FilmController {
private static Film film;
static {
film = new Film();
film.setTitle("Inception");
film.setYear(2010);
film.setGenre("sci-fi");
film.setWatched(true);
}
И добавим этот объект в наш ModelAndView
с помощью метода addObject
:
@RequestMapping(method = RequestMethod.GET)
public ModelAndView allFilms() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("films");
modelAndView.addObject("film", film);
return modelAndView;
}
Теперь мы сможем на нашей странице вывести этот объект. В films.jsp
вместо Hello World напишем ${film}
и сюда подставится объект, которому соответствует имя атрибута "film
". Попробуем запустить и посмотреть что получилось (для понятного вывода объекта у класса Film
был переопределен toString()
):
Model-View-Controller
На данном этапе мы уже, вроде как, имеем полноценное Spring MVC приложение. Прежде чем двигаться дальше хорошо бы еще раз окинуть все взглядом и разобраться как оно все работает. В интернете по этому поводу можно найти множество картинок и схем, мне нравится вот эта:Dispatcher Servlet
, далее он находит для обработки этого запроса подходящий контроллер с помощью HandlerMapping
(это такой интерфейс для выбора контроллера, проверяет в каком из имеющихся контроллеров есть метод, принимающий такой адрес), вызывается подходящий метод и Controller
возвращает информацию о представлении, затем диспетчер находит нужное представления по имени при помощи ViewResolver
'а, после чего на это представление передаются данные модели и на выход мы получаем нашу страничку. Как-то так.
Продолжение следует...
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1)
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 2)
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 3)
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 4)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ