307 lines
15 KiB
Markdown
307 lines
15 KiB
Markdown
## Платформа Java EE
|
||
- Набор стандартов и спецификаций для создания корпоративных приложений на Java
|
||
- Спецификации Java EE реализуются серверами приложений
|
||
- Apache Tomcat
|
||
- Sun/Oracle GlassFish
|
||
- BEA/Oracle WebLogic
|
||
- IBM WebSphere
|
||
- RedHat JBoss
|
||
## Сервлеты
|
||
- [Серверные сценарии](Серверные%20сценарии.md) написанные на Java
|
||
- Жизненным циклов сервлетов управляет веб-контейнер (он же контейнер сервлетов)
|
||
- В отличие от [CGI](CGI.md) запросы обрабатываются в отдельных потоках (а не процессах) на веб-контейнере
|
||
## Преимущества
|
||
- Выполняются быстрее, чем CGI-сценарии
|
||
- Хорошая масштабируемость
|
||
- Надежность и безопасность
|
||
- Платформенно-независимы
|
||
- Множество инструментов мониторинга и отладки
|
||
## Недостатки
|
||
- Слабое разделение уровня представления и бизнес-логики
|
||
- Возможны конфликты при параллельной обработке запросов
|
||
## Обработка HTTP-запроса
|
||
1. Браузер формирует [HTTP](../Сеть%20и%20API/HTTP.md)-запрос и отправляет его на сервер
|
||
2. Веб-контейнер создает объекты `HttpServletRequest` и `HttpServletResponse`
|
||
3. Веб-контейнер вызывает метод `service` у сервлета
|
||
4. Сервлет формирует ответ и записывает его в поток вывода `HttpServletResponse`
|
||
## Пример сервлета
|
||
```Java
|
||
package sample.servlet;
|
||
import javax.servlet.http.HttpServlet;
|
||
import javax.servlet.http.HttpServletRequest;
|
||
import javax.servlet.http.HttpServletResponse;
|
||
import java.io.IOException;
|
||
import java.io.PrintWriter;
|
||
|
||
public class SampleServlet extends HttpServlet {
|
||
@Override
|
||
public void doGet(HttpServletRequest request,
|
||
HttpServletResponse response)
|
||
throws IOException {
|
||
String pageTitle = "Пример сервлета";
|
||
response.setContentType("text/html");
|
||
PrintWriter out = response.getWriter();
|
||
out.println("<html>");
|
||
...
|
||
out.println("</html>");
|
||
}
|
||
}
|
||
```
|
||
## Конфигурация сервлета
|
||
Конфигурирование происходит в `WEB-INF/web.xml` (есть конечно аннотации, но это слишком современно). В `WEB-INF/classes` должны лежать скомпилированные джава-классы
|
||
```xml
|
||
<!-- Регистрируем сервлет -->
|
||
<servlet>
|
||
<servlet-name>SampleServlet</servlet-name>
|
||
<servlet-class>org.itmo.sample.SampleServlet></servlet-class>
|
||
</servlet>
|
||
|
||
<!-- Маппинг -->
|
||
<servlet-mapping>
|
||
<servlet-name>SampleServlet</servlet-name>
|
||
<url-pattern>/sample</url-pattern>
|
||
</servlet-mapping>
|
||
```
|
||
Полный файл `web.xml`
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<web-app>
|
||
|
||
<!-- Регистрируем сервлет -->
|
||
<servlet>
|
||
<servlet-name>SampleServlet</servlet-name>
|
||
<servlet-class>org.itmo.sample.SampleServlet></servlet-class>
|
||
</servlet>
|
||
|
||
<!-- Маппинг -->
|
||
<servlet-mapping>
|
||
<servlet-name>SampleServlet</servlet-name>
|
||
<url-pattern>/sample</url-pattern>
|
||
</servlet-mapping>
|
||
|
||
</web-app>
|
||
```
|
||
## Жизненный цикл сервлета
|
||
- Жизненным циклом управляет веб-контейнер
|
||
- Методы, управляющие жизненным циклом, должен вызывать только веб-контейнер
|
||
### 1. Загрузка класса сервлета
|
||
Проверяются пути: `WEB-INF/classes/`, `WEB-INF/lib/*.jar`, стандартные классы Java SE и классы веб-контейнера
|
||
```xml
|
||
<servlet>
|
||
<servlet-name>SampleServlet</servlet-name>
|
||
<servlet-class>org.itmo.sample.SampleServlet></servlet-class>
|
||
|
||
<!-- Этот параметр можно получить через унаследованный метод getInitParametr(paramName); -->
|
||
<init-param>
|
||
<param-name>say-hello</param-name>
|
||
<param-value>true</param-value>
|
||
</init-param>
|
||
</servlet>
|
||
```
|
||
### 2. Создание экземпляра сервлета
|
||
Создание через конструктор `new SampleServlet()`
|
||
### 3. Вызов метода init()
|
||
`init(config)` - принимает `config`
|
||
### 4. Обработка HTTP-запросов
|
||
На каждый запрос в новом потоке (на самом деле там пул потоков) вызывается метод `service()`
|
||
### 5. Уничтожение сервлета
|
||
Вызов метода `destroy()`
|
||
## Контекст сервлетов
|
||
- API, с помощью которого сервлет может взаимодействовать со своим контейнером
|
||
- Доступ к методам осуществляется через интерфейс `javax.servlet.ServletContext`
|
||
- У всех сервлетов внутри приложения общий контекст
|
||
- В контекст можно помещать общую для всех сервлетов информацию (методы `getAttribute` и `setAttribute`)
|
||
- Если приложение - распределенное, то на каждом экземпляре JVM контейнером создается свой контекст
|
||
```Java
|
||
import java.io.*;
|
||
import javax.servlet.*;
|
||
import javax.servlet.http.*;
|
||
|
||
public class DemoServlet extends HttpServlet {
|
||
@Override
|
||
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
|
||
res.setContentType("text/html");
|
||
PrintWriter pw = res.getWriter();
|
||
|
||
ServletContext context = getServletContext();
|
||
String driverName = context.getInitParameter("dname");
|
||
pw.println("driver name: " + driverName);
|
||
pw.close();
|
||
}
|
||
}
|
||
```
|
||
В `web.xml` до сервлетов
|
||
```xml
|
||
<context-param>
|
||
<param-name>dname</param-name>
|
||
<param-value>org.postgresql.Driver</param-value>
|
||
</context-param>
|
||
```
|
||
## HTTP-сессии
|
||
- Напомним, что HTTP - stateless
|
||
- `javax.servlet.HttpSession` - интерфейс, позволяющий идентифицировать конкретного клиента (браузер) при обработке множества HTTP-запросов от него
|
||
- Экземпляр `HttpSession` создается при первом обращении клиента к предложению и сохраняется некоторое (настраиваемое) время после последнего обращения
|
||
- Идентификатор сессии либо помещается в `cookie`, либо добавляется к URL. Если удалить этот идентификатор, то сервер создаст новую сессию
|
||
- В экземпляр `HttpSession` можно помещать общую для этой сессии информацию (`getAttribute` и `setAttribute`)
|
||
- Сессия привязана к конкретному приложению, у разных приложений - разные сессии
|
||
- В распределенном окружении обеспечивается сохранение целостности данных в HTTP-сессии
|
||
```Java
|
||
public class SimpleSession extends HttpServlet {
|
||
@Override
|
||
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||
response.setContentType("text/html");
|
||
PrintWriter out = response.getWriter();
|
||
HttpSession session = request.getSession();
|
||
|
||
out.println("<html>");
|
||
...
|
||
out.println("session id: " + session.getId());
|
||
out.println("session timeout: " + session.getMaxInactiveInterval());
|
||
session.setMaxInactiveInterval(20*60);
|
||
out.println("new session timeout: " + session.getMaxInactiveInterval());
|
||
...
|
||
out.println("</html>");
|
||
}
|
||
}
|
||
```
|
||
## Диспетчиризация запросов сервлетами
|
||
- Сервлеты могут делегировать обработку запросов другим ресурсам (сервлетам, JSP, HTML-страницам)
|
||
- Диспетчеризация осуществляется с помощью реализаций интерфейса `javax.servlet.RequestDispatcher`
|
||
- Два способа получения `RequestDispatcher` - через `ServletRequest` (абсолютный или относительный URL) и `ServletContext` (только абсолютный URL)
|
||
- Два способа делегирования - `forward` и `include` (в обоих случаях url не меняется)
|
||
- `forward` - полностью отдает запрос другому сервлету или JSP-странице
|
||
- `include` - включает в себя обработку запроса от другого сервлета или JSP-страницы
|
||
- Полный редирект (меняется url в браузере, статус-код 302) - `response.sendRedirect(url)`
|
||
```Java
|
||
public class MyServlet extends HttpServlet {
|
||
@Override
|
||
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
|
||
RequestDispatcher dispatcher = request.getRequestDispatcher("index.jsp");
|
||
dispatcher.forward(request, response);
|
||
}
|
||
}
|
||
```
|
||
## Фильтры запросов
|
||
- Фильтры позволяют осуществлять пред- и постобработку запросов до и после передачи их ресурсу (сервлету, JSP или HTML-странице)
|
||
- Пример предобработки - допуск только авторизованных пользователей
|
||
- Пример постобработки - запись в лог времени обработки запроса
|
||
- Реализуют интерфейс `javax.servlet.Filter`
|
||
- Ключевой метод `doFilter`
|
||
- Метод `doFilter` класса `FilterChain` передает управление следующему фильтру или целевому ресурсу, таким образом возможна реализация последовательностей фильтров
|
||
```Java
|
||
public class MyFilter implements Filter {
|
||
@Override
|
||
public void init(FilterConfig arg0) throws ServletException {}
|
||
|
||
@Override
|
||
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
|
||
PrintWrite out = resp.getWriter();
|
||
out.print("filter is invoked before");
|
||
|
||
chain.doFilter(req, resp); // sends to next resource
|
||
|
||
out.print("filter is invoked after);
|
||
}
|
||
|
||
@Override
|
||
public void destroy() {}
|
||
}
|
||
```
|
||
Конфигурация похожа на конфигурацию сервлетов
|
||
```xml
|
||
<web-app>
|
||
<servlet>
|
||
<servlet-name>s1</servlet-name>
|
||
<servlet-class>HelloServlet</servlet-class>
|
||
</servlet>
|
||
|
||
<servlet-mapping>
|
||
<servlet-name>s1</servlet-name>
|
||
<url-pattern>/servlet1</url-pattern>
|
||
</servlet-mapping>
|
||
|
||
<filter>
|
||
<filter-name>f1</filter-name>
|
||
<filter-class>MyFilter</filter-class>
|
||
</filter>
|
||
|
||
<filter-mapping>
|
||
<filter-name>f1</filter-name>
|
||
<url-pattern>/servlet1</url-pattern>
|
||
</filter-mapping>
|
||
|
||
</web-app>
|
||
```
|
||
> [!important] Порядок сверху вниз, но если использовать аннотации, то порядок недетерминирован
|
||
## Конфигурация аннотациями
|
||
Конфигурация можно писать прямо в Java коде. Tomcat сам сканирует классы и регистрирует компоненты
|
||
### Сервлеты
|
||
`@WebServlet` - замена `<servlet>` и `<servlet-mapping>`
|
||
```Java
|
||
import jakarta.servlet.annotation.WebServlet;
|
||
import jakarta.servlet.http.HttpServlet;
|
||
|
||
@WebServlet("/api/check")
|
||
public class CheckServlet extends HttpServlet {}
|
||
```
|
||
Продвинутый вариант
|
||
```Java
|
||
import jakarta.servlet.annotation.WebServlet;
|
||
import jakarta.servlet.annotation.WebInitParam;
|
||
|
||
@WebServlet(
|
||
name = "MyCheckLogic", // Аналог <servlet-name>
|
||
urlPatterns = {"/api/check", "/run"},// Можно привязать сразу несколько URL!
|
||
loadOnStartup = 1, // Запустить при старте сервера
|
||
initParams = { // Замена <init-param>
|
||
@WebInitParam(name = "adminEmail", value = "admin@itmo.ru"),
|
||
@WebInitParam(name = "maxSize", value = "5000")
|
||
}
|
||
)
|
||
public class CheckServlet extends HttpServlet { ... }
|
||
```
|
||
### Фильтры
|
||
`@WebFilter` - замена `<filter>` и `<filter-mapping>`
|
||
```Java
|
||
import jakarta.servlet.annotation.WebFilter;
|
||
import jakarta.servlet.Filter;
|
||
|
||
@WebFilter(urlPatterns = "/*")
|
||
public class EncodingFilter implements Filter {}
|
||
```
|
||
## Справка по параметрам/аттрибутам
|
||
### Внешние данные от пользователя
|
||
- `getParameter()`
|
||
- Приходит от браузера/клиента по сети
|
||
- Возвращает `String` или `null`
|
||
- Находится в URL или в теле запроса
|
||
- Приоритет у первого query параметра
|
||
- `String user = request.getParameter("red");`
|
||
- Если несколько `String[] colors = request.getParameterValues("color");`
|
||
- Никакого `set` не существует
|
||
### Внутренние данные приложения
|
||
- `getAttribute()` и `setAttribute()`
|
||
- Записывается Java-кодом на сервере. Браузер ничего не знает
|
||
- Возвращает `Object` или `null`, поэтому требует явного каста
|
||
- Внутри запроса `HttpServletRequest` - живет 1 запрос
|
||
- Используется для передачи из Model в View
|
||
- `request.setAttribute("myUser", new User()); User u = (User) request.getAttribute("myUser");`
|
||
- Внутри сессии `HttpSession` - живет пока юзер на сайте
|
||
- `HttpSession session = request.getSession();`
|
||
- `session.setAttribute("role", "ADMIN")`;
|
||
- `String role = (String) session.getAttribute("role");`
|
||
- Внутри контекста `ServletContext` - живет пока работает сервер
|
||
- `ServletContext context = getServletContext();`
|
||
- `context.setAttribute()` и `context.getAttribute()`
|
||
### Метаданные HTTP
|
||
- `getHeader()`
|
||
- Приходит от браузера в служебных строках
|
||
- Возвращает `String` или `null`
|
||
- `request.getHeader("Authorization");`
|
||
### Статические настройки из файла конфигурации
|
||
- `getInitParameter()`
|
||
- Читается сервером из файла `web.xml` или аннотаций
|
||
- Возвращает `String` или `null`
|
||
- `String email = getServletConfig().getInitParameter("email");` - для сервлета
|
||
- `String dbUrl = getServletContext().getInitParameter("dbUrl");` - для всех сервлетов
|