JavaRush /Java блог /Random UA /Веб-додаток на Java
Viacheslav
3 рівень

Веб-додаток на Java

Стаття з групи Random UA
Веб-додаток на Java - 1

Вступ

Колись Java зміцнив свої позиції завдяки тому, що вибрав пріоритетним напрямком веб-програми. З перших днів Java намагався знайти свій шлях. Спочатку – запропонував аплети. Це надало багато можливостей розробникам створення динамічного контенту (вмісту) на статичних HTML сторінках. Проте аплети не виправдали очікувань із багатьох причин: безпека, накладні витрати та інші. Тоді розробники мови Java запропонували альтернативу - Servlet API . І це виявилося правильним рішенням. Servlet API — це специфікація, на якій побудовано будь-який веб-додаток на Java, будь то програма з веб-інтерфейсом або веб-сервіс, який повертає інформацію згідно з запитом. Тому шлях до розуміння роботи веб-додатків Java починається з розуміння Servlet API.
Веб-додаток на Java - 2

Servlet API

Отже, Servlet API це те, що запропонували розробники мови Java-розробникам. Servlet API — це специфікація, яка має відповідати на наші основні питання. Знайти її можна тут: " JSR-000340 JavaTM Servlet 3.1 Final Release for Evaluation ". У розділі " 1.1 What is a Servlet? " сказано, що сервлет - це веб-компонент , заснований на Java-технології, який створює динамічний контент (тобто вміст). "Заснований на Java-технології" означає, що сервлет - це Java-клас, скомпільований в байт-код . Сервлети керуються контейнером сервлетів, який іноді називають Servlet Engine. Сервлет контейнер — це розширення веб-сервера, що надає функціональність сервлетів У свою чергу, сервлети забезпечують взаємодію з клієнтом у парадигмі запит/відповідь, яка й реалізується сервлет-контейнером. У розділі " 1.2 What is a Servlet Container? " сказано, що сервлет контейнер - це частина веб-сервераабо сервера додатків, який надає мережеві сервіси, за допомогою яких надсилаються запити та відповіді, формуються та обробляються MIME-based запити та відповіді. Крім того, сервлет контейнери керують життєвим циклом сервлетів (тобто вирішують, коли їх створювати, видаляти тощо). Всі сервлет контейнери повинні підтримувати протокол HTTP для отримання запитів та надсилання відповідей. Тут хочеться додати, що MIME — це такий стандарт, специфікація, яка каже, як треба кодувати інформацію та форматувати повідомлення, щоб їх можна було пересилати інтернетом.
Веб-додаток на Java - 3

Web-server

Веб-сервер - це сервер, який приймає HTTP-запити від клієнтів і видає їм HTTP відповіді (як правило, разом із HTML сторінкою, зображенням, файлом або іншими даними). Затребувані ресурси дізнаються URL-адресаами. Одним із найпопулярніших веб-серверів з підтримкою Servlet API є Apache Tomcat . Більшість веб-серверів складні механізми, які складаються з різних компонентів, і кожен з них виконує певні функції. Наприклад:
Веб-додаток на Java - 4

Connectors

— На вході ми маємо Connectors (тобто конектори), які приймають вхідні запити від клієнтів. HTTP конектор Tomcat реалізований за допомогою компонента "Coyote". Конектори приймають дані від клієнта і передають їх у Tomcat Engine. Servlet Container - Tomcat Engine у ​​свою чергу обробляє отриманий від клієнта request за допомогою компонента "Catalina", який є сервлет контейнером. Докладніше див. документацію Tomcat: " Architecture Overview ". Існують і інші веб-сервери, що підтримують специфікацію Servlet API. Наприклад, " Jetty " або " Undertow ". Їхня архітектура схожа, тому розуміючи принцип роботи з одним сервлет контейнером, можна перебудуватися на роботу з іншим.
Веб-додаток на Java - 5

Веб-додаток (Web Application)

Отже, щоб ми змогли запустити веб-програму, нам потрібен веб-сервер, який підтримує Servlet API (тобто в якому є компонент-розширення, що реалізує для веб-сервера підтримку Servlet API). Добре. А що ж таке взагалі веб-додаток? Згідно з розділом " 10 Web Applications " специфікації Servlet API, Web application - це набір сервлетів, HTML сторінок, класів та інших ресурсів, які становлять кінцевий додаток на веб-сервері. Згідно з розділом " 10.6 Web Application Archive File ", веб-додаток може бути запакований в Web ARchive (архів з розширенням WAR). Як сказано на сторінці " Glossary-219 ":
Веб-додаток на Java - 6
Тобто WAR зроблено замість JAR, щоб показати, що це веб-програма. Наступний важливий факт: маємо бути певна структура каталогів у нашому WAR архіві. У специфікації Servlet API у розділі " 10.5 Directory Structure ". У цьому розділі сказано, що є особливий каталог, який називається "WEB-INF". Даний каталог особливий тим, що він не видно клієнту і йому не показується, але він доступний для коду сервлетів. Також сказано, що може містити каталог WEB-INF:
Веб-додаток на Java - 7
З цього списку нам тепер невідомий і незрозумілий пункт про якийсь файл web.xml , званий deployment descriptor (дескриптор розгортання). Що це таке? Деплойменту дескриптору відведено розділ " 14. Deployment Descriptor ". Якщо коротко, то деплоймент дескриптор - це такий xml файл, який описує, як потрібно розгортати (тобто запускати) на веб-сервері наш веб-додаток. Наприклад, у деплоймент дескрипторі вказується за якими URL потрібно звертатися до нашого додатку, вказуються налаштування безпеки, які відносяться до нашого додатку і т.д. У розділі " 14.2 Rules for Processing the Deployment" сказано, що web.xml перш ніж наш додаток буде налаштовано і запущено буде провалідовано за схемою (тобто буде виконано перевірку, що вміст web.xml написано правильно згідно зі схемою). А в розділі " 14.3 Deployment Descriptor " зазначено, що схема лежить тут: http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd Якщо подивитися на вміст файлу, ми можемо побачити:
Веб-додаток на Java - 8
Навіщо використовується схема для XML файлів? У схемах зазначено, як правильно заповнювати XML документ: які елементи можна використовувати, якого типу дані в елементах можуть бути вказані, в якому порядку елементи повинні йти, які обов'язкові елементи і т.д. Можна порівняти схему XML документа з інтерфейсом Java, адже схема Java також вказує, яким чином повинні бути написані класи, які задовольняють даному інтерфейсу (тобто які реалізують даний інтерфейс). Отже, ми озброєні таємними знаннями і готові створювати свій перший веб-додаток!
Веб-додаток на Java - 9

Створення веб-програми

Роботи із сучасним Java-додатком вже важко уявити без використання систем автоматичного складання проектів. Одними з найпопулярніших систем є Maven та Gradle . Скористайтеся даним оглядом Gradle. Установка Gradle описана на офіційному сайті . Для створення нової програми нам знадобиться вбудований у Gradle плагін: " Build Init Plugin ". Для створення Java-програми необхідно виконати таку команду: gradle init --type java-application
Веб-додаток на Java - 10
Після створення проекту нам знадобиться відредагувати файл build.gradle . Це так званий Build Script (докладніше див. документацію Gradle: " Writing Build Scripts "). У цьому файлі описується те, як необхідно збирати проект та інші аспекти роботи з Java проектом. У блоці plugins описується, які " Gradle плагіни " необхідно використовувати для поточного Gradle проекту. Плагіни розширюють можливості нашого проекту. Наприклад, за умовчанням використовується плагін " java ". Цей плагін завжди використовується, якщо нам потрібна підтримка Java. Але ось плагін " application" нам не потрібен, тому що в його описі вказано, що він служить для створення "executable JVM application", тобто виконуваних JVM додатків. Нам же потрібне створення Web додатка у вигляді WAR архіву. пошукаємо слово WAR, знайдемо і " War Plugin ". Отже, вкажемо наступні плагіни:
plugins {
    id 'java'
    id 'war'
}
Так само в " War Plugin Default Settings " сказано, що каталог з усім вмістом веб-програми повинен бути "src/main/webapp", там повинен лежати той самий каталог WEB-INF, в якому повинен лежати web.xml. Створимо такий файл. Заповнювати його будемо трохи пізніше, т.к. ми ще не маємо достатньо інформації для цього. У блоці "dependencies" вказуємо залежності нашого проекту, тобто бібліотеки/фреймворки, без яких не може працювати наша програма. В даному випадку ми пишемо веб-додаток, а значить ми не можемо працювати без Servlet API:
dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testCompile 'junit:junit:4.12'
}
Забезпеченакомпіле говорить про те, що залежність не потрібно включати в наш WAR архів веб-додатків: вона потрібна тільки для компіляції. А при виконанні цю залежність буде надано кимось іншим (тобто веб-сервером). Ну і залишаємо в build script інформацію про те, який репозиторій залежностей ми хочемо використовувати - з нього будуть завантажені всі вказані dependencies:
repositories {
    jcenter()
}
Решта з файлу build script'а прибираємо. Тепер відредагуємо клас src\main\java\App.java. Зробимо з нього сервлет. У специфікації Servlet API у розділі " CHAPTER 2. The Servlet Interface " сказано, що Servlet Interface має базову реалізацію HttpServlet , якої має досить у більшості випадків і розробникам достатньо успадкуватися від неї. На главі " 2.1.1 HTTP Specific Request Handling Methods " вказані основні методи, які обробляють вхідні запити. Таким чином, перепишемо клас App.java:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.IOException;

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

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 		// https://www.oracle.com/technetwork/java/servlet-142430.html
 		PrintWriter out = resp.getWriter();
 		out.println(this.getGreeting());
 		out.close();
 	}
}
Отже, у нас начебто все готове. Залишилося правильно написати дескриптор розгортання. Зі схеми скопіюємо в web.xml наступний текст:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="..."
      version="3.1">
      ...
</web-app>
А також шлях до схеми, який вказаний у ній: http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd Тепер подивимося приклад того, як має виглядати web.xml у специфікації Servlet API. Цей приклад наведено у розділі " 14.5.1 A Basic Example " . Сумісний те, що вказано у схемі з прикладом, який вказаний у специфікації. Отримаємо таке:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
      version="3.1">
      <display-name>A Simple Web Application</display-name>
      <servlet>
		<servlet-name>app</servlet-name>
		<servlet-class>App</servlet-class>
	  </servlet>
	  <servlet-mapping>
		<servlet-name>app</servlet-name>
		<url-pattern>/app</url-pattern>
	  </servlet-mapping>
</web-app>
Як видно, ми використовували схему і schemaLocation, які були вказані раніше. А опис самих елементів взяли з прикладу розділу 14.5.1. Якщо ми зробабо правильно, виконаємо gradle завдання gradle war без помилок:
Веб-додаток на Java - 11
Веб-додаток на Java - 12

Запуск веб-програми

Як же відбувається запуск веб-програми? Давайте розберемося спочатку з складнішим варіантом. Ми раніше говорабо, що Apache Tomcat є веб-сервер, який підтримує Servlet API. А це означає, що наш зібраний war архів ми зможемо розгорнути (ще кажуть "задеплоїти") на цьому сервері. На сторінці " Download Tomcat " завантажуємо з розділу "Binary Distributions" тип постачання "Core" у форматі zip. І розпакуємо завантажений архів у якийсь каталог, наприклад в C:\apache-tomcat-9.0.14. Перш ніж запускати сервер, відкриємо на редагування файл conf\tomcat-users.xmlі додамо до нього наступний рядок: <user username="tomcat" password="tomcat" roles="tomcat,manager-gui,admin-gui"/> Тепер у командному рядку переходимо до каталогу bin і виконуємо catalina.bat start. За промовчанням консоль сервера буде доступна за адресаоюhttp://localhost:8080/manager. Логін та пароль ті самі, які ми вказали у tomcat-users.xml. Tomcat' має каталог "webapps", в якому лежать веб-додатки. Якщо ми хочемо розгорнути своє, ми маємо скопіювати туди свій war архів. Коли ми раніше виконали команду gradle war, то в каталозі \build\libs\створився архів war. Ось його нам і треба скопіювати. Після копіювання оновимо сторінку http://localhost:8080/managerта побачимо:
Веб-додаток на Java - 13
Виконавши http://localhost:8080/javaweb/appми звернемося до нашого сервлета, т.к. звернення /app раніше ми "замапабо" (тобто зіставабо) на сервлет App. Існує швидший спосіб перевірити, як працює програма. І в цьому нам знову допомагає система збирання. У build script нашого Gradle проекту ми можемо додати до секції plugins новий плагін " Gretty ": id "org.gretty" version "2.3.1" І тепер ми можемо виконувати gradle task для запуску нашої програми:gradle appRun
Веб-додаток на Java - 14
Докладніше див. " Add gretty plugin and run the app ".
Веб-додаток на Java - 15

Spring та Servlet API

Сервлети – основа всього. І навіть популярний зараз Spring Framework ніщо інше, як надбудова над Servlet API. Для початку Spring Framework – це нова залежність для нашого проекту. Тому додамо її в build script в блок dependencies: compile 'org.springframework:spring-webmvc:5.1.3.RELEASE' У документації Spring Framework є розділ " 1.1. DispatcherServlet ". У ній сказано, що Spring Framework побудований за шаблоном "front controller" - це коли є центральний сервлет, який називається " DispatcherServlet ". Всі запити надходять на цей сервлет, а він уже делегує виклики потрібним компонентам. Бачите, навіть тут сервлети. До деплойменту дескриптор необхідно додати listener (слухача):
<listener>
	&ltlistener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Це слухач подій сервлет контексту. Тобто коли стартує Servlet Context, стартує і контекст Spring'а (WebApplicationContext). Що таке Servlet Context? Він описаний у специфікації Servle API у розділі " CHAPTER 4. Servlet Context " . Сервлет контекст - це "погляд" сервлетів на веб-додаток, всередині якого сервлети запущені. Кожна веб-програма має свій Servlet Context. Далі для включення Spring Framework необхідно вказати context-param - параметр ініціалізації сервлет контексту.
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
І завершує конфігурацію визначення DispatcherServlet :
<servlet>
	<servlet-name>app</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
 		<param-value></param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>app</servlet-name>
	<url-pattern>/</url-pattern>
</servlet-mapping>
І тепер нам залишилося заповнити файл, вказаний у contextConfigLocation. Як це зробити, описано в документації Spring Framework у розділі "1.3.1. Declaration":
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.codegym.javaweb"/>
    <mvc:annotation-driven/>
</beans>
Тут важливо не лише вказати, який пакет сканувати, а й те, що ми хочемо annotation-driven, тобто управляти інструкціями тим, як працюватиме Spring. Залишається тільки створити пакет com.codegym.javaweb та розмістити в ньому клас Spring контролера:
package ru.codegym.javaweb;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SpringController {

    @GetMapping("/app")
    @ResponseBody
    public String getGreeting() {
        return "Hello world.";
    }
}
Запустивши тепер gradle appRun і перейшовши за адресаою, http://127.0.0.1:8080/javaweb/appми отримаємо все той же Hello World. Як видно, Spring Framework тісно переплітається з Servlet API і використовує його, щоб працювати поверх нього.
Веб-додаток на Java - 16

Анотації

Як ми бачабо, анотації це зручно. І не ми одні так подумали. Тому в специфікації Servlet API починаючи з версії 3.0 з'явилася глава " CHAPTER 8 Annotations and pluggability ", яка вказує, що контейнер контейнери повинні підтримувати можливість вказувати те, що раніше вказувалося в Deployment Descriptor'е через анотації. Таким чином, web.xml можна зовсім видалити з проекту, а над класом сервлету вказувати інструкцію @WebServletі вказувати, який шлях "мапити" сервлет. Тут начебто все зрозуміло. Але що робити, якщо ми до проекту підключабо Spring, який потребує складніших налаштувань? Тут все трохи складніше. По-перше, у документації Spring сказано, що для налаштування Spring без web.xml потрібно використовувати свій клас, який буде реалізовувати WebApplicationInitializer. Докладніше див. розділ " 1.1. DispatcherServlet ". Виходить, що це клас Spring'а. Як тоді використовується Servlet API тут? Насправді, Servlet API 3.0 був доданий ServletContainerInitializer . Використовуючи особливий механізм Java (називається SPI ), Spring вказує свій ініціалізатор сервлет контейнера, який називаєтьсяSpringServletContainerInitializer. У свою чергу, він вже шукає реалізації WebApplicationInitializer і викликає потрібні методи та виконує потрібні налаштування. Докладніше див. " How servlet container finds WebApplicationInitializer implementations ". Наведені вище настройки можна виконати так:
package ru.codegym.javaweb.config;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class AppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        // регистрируем конфигурацию созданую высше
        ctx.register(AppConfig.class);
        // добавляем в контекст слушателя с нашей конфигурацией
        servletContext.addListener(new ContextLoaderListener(ctx));

        ctx.setServletContext(servletContext);

        // настраиваем маппинг Dispatcher Servlet-а
        ServletRegistration.Dynamic servlet =
                servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }
}
Тепер за допомогою Java-based configuration вкажемо, який пакет сканувати + включимо анотації:
package ru.codegym.javaweb.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.codegym.javaweb.controllers")
public class AppConfig {
}
А сам SpringController був перенесений до com.codegym.javaweb.controllers, щоб при скануванні конфігурація не знаходила сама себе, а шукала лише контролери.
Веб-додаток на Java - 17

Підбиття підсумків

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