JavaRush /Java блог /Random UA /Від Hello World до Spring Web MVC і до чого тут сервлети
Viacheslav
3 рівень

Від Hello World до Spring Web MVC і до чого тут сервлети

Стаття з групи Random UA
Від Hello World до Spring Web MVC і до чого тут сервлети - 1

Вступ

Як ми знаємо, успіх Java прийшов саме завдяки еволюції програмного забезпечення, яке прагне в мережу. Тому, і ми візьмемо за основу звичайний консольний додаток " Hello World " і зрозуміємо, що йому потрібно, щоб стати з консольної програми мережевим додатком. Отже, спочатку потрібно створити Java проект. Програмісти люди ліниві. У доісторичні часи, коли одні полювали на мамонтів, інші сиділи і намагалися не заплутатися у різноманітності Java бібліотек, структурах каталогів. Щоб розробник міг керувати процесом створення програми, щоб він зміг просто написати "хочу бібліотеку таку версію 2" придумали спеціальні засоби - системи складання. Дві найвідоміші з них: Maven та Gradle. Для цієї статті ми скористаємось Gradle. Якщо раніше нам довелося б самим створити структуру каталогів, то зараз Gradle за допомогою плагіна Gradle Init Plugin дозволяє створити Java проект зі структурою каталогів та базовим Main класом в одну команду: gradle init --type java-application Ця команда виконує ініціалізацію (init) для нас Java-додаток (java-application ) з консольним Hello World. Після завершення в каталозі з'явиться файл build.gradle . Це наш build script — це такий собі сценарій створення програми з описом того, які дії для цього потрібно виконувати. Відкриємо його та додамо до нього рядок: jar.baseName = 'webproject' Gradle дозволяє виконувати різні дії над проектом і ці дії називаються tasks. За допомогою виконання команди (task'а) gradle buildу каталозі /build/libs буде створено JAR файл. І як Ви здогадалися, ім'я йому тепер буде webproject.jar . Але якщо виконаємо java -jar ./build/libs/webproject.jar, то отримаємо помилку: no main manifest attribute. Все тому, що для java програми потрібно прикласти якийсь маніфест - це такий опис, як працювати з додатком, як його сприймати. Тоді JVM, яка і буде виконувати Java додаток буде знати, який клас є точкою входу в програму та іншу інформацію (наприклад, classpath). Якщо уважніше придивитися до вмісту build скрипта, то ми побачимо плагіни, що підключаються. Наприклад: apply plugin: 'java' Якщо зайти на сторінку плагіна Gradle Java Plugin , то можемо побачити, що ми можемо сконфігурувати маніфест:
jar {
    manifest {
        attributes 'Main-Class': 'App'
    }
}
Головний клас, точку входу до програми, для нас згенерував Gradle Init Plugin. І вона навіть вказана у параметрі mainClassName. Але це не підійшло, т.к. цей параметр відноситься до іншого плагіна, Gradle Application Plugin . Отже, у нас є Java програма, яка виводить Hello World на екран. Java додаток це упаковується у JAR (Java ARchive). Воно просте, консольне, не актуальне. Як же перетворити його на веб-додаток?
Від Hello World до Spring Web MVC і до чого тут сервлети - 2

Servlet API

Для того щоб Java змогла працювати з мережею ще в далекі часи з'явилася специфікація, звана Servlet API . Саме дана специфікація описується клієнт-серверна взаємодія, отримання повідомлення від клієнта (наприклад, браузера) та надсилання відповіді (наприклад, з текстом сторінки). Природно, з того часу багато що змінилося, але суть у тому, щоб Java програма стала веб-додатком використовується Servlet API. Щоб не міркувати голослівно, візьмемо до рук ту саму специфікацію: JSR-000340 JavaTM Servlet 3.1 . Насамперед, нас цікавить " Chapter 1: Overview ". У ній описуються основні поняття, які ми маємо усвідомити собі. По-перше, що таке серврлет? У розділі " 1.1 What is a Servlet? " сказано, щоServlet - це Java компонент, який керується контейнером і який генерує динамічний зміст. Як і інші Java компоненти, сервлет є Java класом, який скомпільований в байт-код і який може бути завантажений у веб-сервер, що використовує технологію Java. Важливо, що сервлети взаємодіють із веб-клієнтом (наприклад, браузером) у рамках парадигми запит/відповідь (request/response), яку реалізує Servlet Container. Виходить, що Servlet'и живуть у якомусь Servlet Container'і. Що це? У розділі " 1.2 What is a Servlet Container? " сказано, що Servlet Container— це деяка частина веб-сервера або сервера програм, яка надає мережеві сервіси, через які надсилаються запити та відповіді надсилаються. Цей Servlet Container керує життєвим циклом сервлетів. Усі Servlet Container'и повинні підтримувати протокол HTTP як мінімум, але можуть підтримувати інші. Наприклад, HTTPS. Також важливо, що саме Servlet Container може накладати на оточення, в якому виконуються сервлети, які пов'язані з безпекою обмеження. Важливо також, що відповідно до " 10.6 Web Application Archive File " веб-додаток має бути упаковано в WAR (Web ARchive) файл. Тобто тепер нам потрібно видалити наші jar та application плагіни на щось інше. І це – Gradle WAR plugin. А замість jar.baseName вказати war.baseName Т.к. ми не використовуємо більше jar плагін, то ми видалабо і налаштування manifest. Коли ми запускали JAR - віртуальній машині Джава (JVM) потрібно було через маніфест підказати, як працювати з нашим додатком. Тому що JVM виконувала його. Веб-додаток, зважаючи на все, виконуємо якийсь веб-сервер. Виходить, йому треба якось підказати, як працювати з нашим веб-додатком? І виявляється що так. У веб-застосунків свій, особливий маніфест. Називається він Deployment Descriptor . Йому відведено цілий розділ: " 14. Deployment Descriptor ". Є важливий розділ: " Chapter 10: Web Applications ". Він розповідає про те, що являє собою веб-додаток з точки зору Servlet API. Наприклад, у розділі "10.5 Directory Structure "вказано, де має бути Deployment Descriptor: /WEB-INF/web.xml. Куди розмістити WEB-INF? Як сказано в Gradle WAR plugin, він додає новий layout :. src/main/webappТому створимо такий каталог, всередині створимо каталог WEB-INF, а всередині створимо файл web. xml Важливо, щоб каталог називався WEB-INF, а не META-INF! Скопіюємо собі з прикладу XML
Від Hello World до Spring Web MVC і до чого тут сервлети - 3
Як бачимо, для конфігурації використовується XML документ. XML документ, щоб вважатися коректним (Valid), повинен відповідати якійсь "схемі". Можна вважати це своєрідним інтерфейсом для XML документа. У схемі вказано, які елементи можуть бути у XML документі, якого типу дані можуть задавати елемент, порядок, обов'язковість та інші аспекти. У скопійованому з документації прикладі вказано версію 2.5, а ми хочемо використовувати версію 3.1. Звичайно, специфікація зі зміною версій змінювалася, додавали нові можливості. Тому схему потрібно використовувати не ту, яку використовували для версії 2.5 (web-app_2_5.xsd). Яку схему використовувати для версії 3.1? У цьому нам допоможе документація, розділ " 14.3 Deployment Descriptor ", у якій зазначеноspecification is available at http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd Тобто нам треба скрізь замінити посилання на схему на вказану xsd, не забувши поміняти version="2.5"на 3.1, а так само змінити скрізь namespace (xmlns і в xsi:schemaLocation). Вони вказують, у межах якого простору імен ми працюватимемо (якщо дуже просто, то які імена елементів ми можемо використовувати). Якщо відкрити файл схеми, то в targetNamespace буде вказано той самий namespace, який ми повинні вказувати:
Від Hello World до Spring Web MVC і до чого тут сервлети - 4
Як ми пам'ятаємо, у Manifest Jar-файлу ми писали, який клас ми хочемо використати. Що ж тут робити? Тут необхідно вказати, який клас сервлета ми хочемо використовувати, коли отримаємо запит від веб-клієнта. Опис можна прочитати у розділі " 14.4 Deployment Descriptor Diagram ". Виглядати це буде так:
Від Hello World до Spring Web MVC і до чого тут сервлети - 5
Тут все просто. Оголошується серверлет, а далі робиться його мапінг на деякий шаблон. У разі, на /app. Коли спрацює шаблон, виконання буде запущено метод сервлета. Для краси клас App варто перенести в пакет, не забувши виправити xml конфігурацію. Але це ще не все. App має бути сервлетом. Що це означає бути серврлетом? Це означає, що ми повинні успадковуватися від HttpServlet . Приклад можна підглянути у розділі " 8.1.1 @WebServlet ". Відповідно до нього наш App клас виглядатиме так:
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class App extends HttpServlet {
    public String getGreeting() {
        return "Hello world.";
    }

	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		response.setContentType("text/html");
		try {
			response.getWriter().println(getGreeting());
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}
}
Але наш проект ще не готовий. Тому що ми тепер залежимо від Servlet API версії 3.1. А це означає, що в нашому скрипті build треба вказати залежність від Servlet API. Адже JVM треба знати, що те, що Ви написали в коді правильно, як це використовувати. Як ми пам'ятаємо, специфікація — це лише інтерфейси, які описують, як це все має працювати. А продажі лежать на стороні веб-сервера. Тому без Servlet API буде знаходити потрібну бібліотеку на Maven Central: javax.servlet-api . І додаємо запис до блоку dependencies . У Maven репозиторії, як Ви бачабо, вказано наведене. Перед використанням залежності слід вказати scope. У Gradle немає scope з ім'ям "provided", зате там є scope " compile only ". Тому вкажемо:providedCompile 'javax.servlet:javax.servlet-api:3.1.0' Уф, начебто все? Gradle Build збере наш проект у файл WAR. І що далі з ним робити? Для початку нам потрібний Web Server. У гугле пишемо " web server java list " і бачимо список веб-серверів. Виберемо з цього списку, наприклад, TomCat . Переходимо на сайт Apache Tomcat , завантажуємо останню версію (на даний момент 9 версія) у вигляді zip архіву (якщо для Windows). Розпаковуємо в якийсь каталог. Ура, ми маємо веб-сервер. З каталогу веб-сервера в підготовці bin виконуємо з командного рядка catalina і бачимо доступні опції. Виконаємо:catalina start. Кожен веб-сервер має каталог, за яким веб-сервер стежить. Якщо там з'являється файл веб-застосунку, то веб-сервер починає його в себе встановлювати. Така установка називається розгортанням або деплоєм ( deploy ). Так, саме тому " deployment descriptor ". Тобто як потрібно правильно розгорнути програму. У Tomcat такий каталог - webapps . Скопіюємо туди war, котрий ми зробабо за допомогою gradle build. Після цього в лозі ми побачимо щось на зразок: Deployment of web application archive [tomcat\webapps\webproject.war] has finished in [время] ms Щоб розуміти ще краще, в каталозі tomcat відредагуємо файл \conf\tomcat-users.xml, додавши наступні рядки:
Від Hello World до Spring Web MVC і до чого тут сервлети - 6
Тепер перезапускаємо сервер (catalina stop, catalina start) і переходимо за адресаою http://127.0.0.1:8080/manager Тут ми побачимо шляхи всіх програм. Нашому webproject швидше за все дали шлях / webproject. Що це за шлях? У специфікації у розділі " 10.1 Web Applications Within Web Servers " сказано, що веб-додаток асоціюється з деяким шляхом усередині програми (у разі, це /webproject). Всі запити через цей шлях будуть асоційовані з тим самим ServletContext'ом. Цей шлях ще називається contextRoot . А згідно " 10.2 Relationship to ServletContext " сервлет контейнер співвідносить веб-додаток і ServletContext один до одного. Тобто кожен веб-додаток має свій ServletContext. Що ж таке ServletContext? Як говорить специфікація, ServletContext - це деякий об'єкт, який надає сервлетам деяке уявлення про додаток, в якому вони виконуються, " view of the application ". Докладніше про Servlet Context розповідається в розділі 4 специфікації Servlet API. Дивно, що Servlet API у версії 3.1 не вимагає обов'язкової присутності web.xml. Наприклад, можна задати серврлет за допомогою анотацій:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/app2")
public class App2 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        response.getWriter().println("app2");
    }
}
За темою так само рекомендуються: " Співбесіда з Java EE - JEE Servlet API (питання та відповіді) ". Отже, у нас є Servlet - він відповідає за те, яку відповідь видати веб-клієнту. У нас є ServletContainer, який отримує запити від користувача, співвідносить шлях, до якого звернулися, шляхом сервлета і якщо відповідність знайдена - виконує Servlet. Добре. Яке ж місце у цій картині світу займає Spring ?

Spring Web MVC

Добре, у нас є веб-додаток. Тепер нам потрібно підключити Spring. Як нам це зробити. По-перше, потрібно розібратися з тим, як правильно підключити Spring до проекту. Виявляється, раніше можна було це робити у відповідності з документацією проекту Spring platform , але тепер " The Platform will reach the end of its supported life on 9 April 2019 ", тобто бажано її використовувати, т.к. скоро вона перестане підтримуватись. Єдиний вихід - " Users of the Platforms are encourage to start using Spring Boot's dependency management ". Тому перейдемо до документації Spring Boot. Уточню, що ми не використовуємо сам Spring Boot, а лише Dependency Management від Spring Boot. Тобто, проект Spring Boot може надавати знання про те, які версії бібліотек потрібно використовувати (у тому числі Spring MVC). Там знайдемо 3.2. Using Spring Boot's dependency management in isolation . Згідно з документацією в build скрипт додаємо:
plugins {
    id 'org.springframework.boot' version '2.0.4.RELEASE' apply false
}
apply plugin: 'io.spring.dependency-management'
і
dependencyManagement {
    imports {
        mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
    }
}
Як бачимо, ми вказали apply false, тобто. не застосовуємо сам Spring Boot, але використовуємо туди управління залежностями. Таке управління залежностями так само називають BOM - " Bill Of Materials ". Тепер ми готові підключати сам проект Spring Web MVC. Spring Web MVC є частиною проекту Spring Framework і нас цікавить розділ " Web Servlet ". Додамо залежність у build script: compile 'org.springframework:spring-webmvc'. Як бачимо, ми виставабо scope compile, т.к. веб-сервер не надає нам Spring. Наш проект змушений включати бібліотеку Spring усередину себе. Далі нам важливо прочитати розділ " 1.2. DispatcherServlet " , де сказано, що Spring MVC побудований навколо шаблону " Front controller", коли є якийсь центральний сервлет, який надає конфігурацію та делегування іншим компонентам. Dispatcher можна перекласти як диспетчер. Отже, насамперед у web.xml оголошуємо:
Від Hello World до Spring Web MVC і до чого тут сервлети - 7
Як бачимо, це насправді звичайний Listener, визначений у специфікації Servlet API. Якщо бути більш точним, то це ServletContextListener, тобто він спрацьовує на ініціалізацію Servlet Context'а для нашого веб-додатку. Далі треба вказати налаштування, яке розповість Spring, де лежить його особливий xml конфіг з налаштуваннями:
Від Hello World до Spring Web MVC і до чого тут сервлети - 8
Як видно, це просто звичайне налаштування, яке зберігається на рівні Servlet Context, але яке буде використане Spring'ом при ініціалізації Application Context. Тепер потрібно оголосити замість всіх сервлетів один єдиний диспетчер, що розподіляє всі інші запити.
Від Hello World до Spring Web MVC і до чого тут сервлети - 9
І тут жодної магії. Якщо ми подивимося, це HttpServlet, просто в якому Spring виконує багато всього, що робить його фреймворком. Залишилося співвіднести (змапити) певний шаблон url із сервлетом:
Від Hello World до Spring Web MVC і до чого тут сервлети - 10
Все, як ми робабо раніше. Тепер давайте створимо щось, що має показати наш веб-сервер. Наприклад, створимо у нашому WEB-INF підкаталог pages, а там файл hello.jsp. Вміст може бути найпримітивнішим. Наприклад, усередині тегів html тег h1 з текстом " Hello World ". І не забудемо створити файл applicationContext.xml, який ми вказали раніше. Візьмемо приклад із документації Spring: " 1.10.3. Automatically detecting classes and registering bean definitions ".
Від Hello World до Spring Web MVC і до чого тут сервлети - 11
Т.к. ми включаємо таким чином автовизначення, то ми можемо тепер створити 2 класи (вони будуть вважатися Spring Bean'ами через використання спеціальних Spring анотацій), які Spring тепер сам створить і виконає за їх допомогою доналаштування нашого додатку:
  1. Веб-конфігурація для прикладу конфігурування в стилі Java:

    @Configuration
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp("/WEB-INF/pages/", ".jsp");
        }
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();
        }
    }

    Даний приклад описаний у документації Spring Framework: " 1.11. MVC Config ".

    Тут ми реєструємо ViewResolver, який допомагатиме визначити, як знаходитись jsp сторінки. Другий метод забезпечує включення " Default servlet ".

    Докладніше можна прочитати про це тут: " У чому полягає необхідність та використання default-servlet-handler ".

  2. Контролер HelloController для опису мапінгу запитів на певний JSP

    @Controller
    public class HelloController {
        @GetMapping("/hello")
        public String handle(Model model) {
            return "hello";
        }
    }

    Тут ми використали інструкцію @Controller, описану в документації в розділі " 1.4. Annotated Controllers ".

Тепер, коли наша програма буде розгорнута, то коли ми надішлемо запит /webproject/hello(де /webproject — це context root), то спочатку відпрацьовуватиме DispatcherServlet. Він, як головний диспетчер, визначить, що ми /* підходить під поточний запит, це DispatcherServlet повинен щось робити. Далі він пройдеться по всіх мапінгах, які знайде. Побачить, що є HelloController з методом handle, який замалений на /hello та виконає його. Цей метод поверне текст "hello". Цей текст отримає ViewResolver, який розповість серверу, де шукати jsp файли, які потрібно відобразити клієнту. Таким чином зрештою клієнт отримає ту саму заповітну сторінку.

Висновок

Сподіваюся, зі статті буде зрозуміло, що слово "контекст" це не страшно. Що специфікації, виявляються, дуже корисні. А документація – наш друг, а не ворог. Сподіваюся, буде зрозуміло, на чому заснована робота Spring, як він підключається і до чого тут Servlet API.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ