Где инициализируется контекст приложения в mvc
Перейти к содержимому

Где инициализируется контекст приложения в mvc

  • автор:

Spring – MVC Framework

Среда Spring Web MVC предоставляет архитектуру Model-View-Controller (MVC) и готовые компоненты, которые можно использовать для разработки гибких и слабосвязанных веб-приложений. Шаблон MVC приводит к разделению различных аспектов приложения (логика ввода, бизнес-логика и логика пользовательского интерфейса), обеспечивая при этом слабую связь между этими элементами.

  • Модель инкапсулирует данные приложения и в целом они состоят из POJO.
  • Представление отвечает за рендеринг данных модели и, в общем, генерирует вывод HTML, который может интерпретировать браузер клиента.
  • Контроллер отвечает за обработку пользовательских запросов и построение соответствующей модели и передает ее в представление для визуализации.

Модель инкапсулирует данные приложения и в целом они состоят из POJO.

Представление отвечает за рендеринг данных модели и, в общем, генерирует вывод HTML, который может интерпретировать браузер клиента.

Контроллер отвечает за обработку пользовательских запросов и построение соответствующей модели и передает ее в представление для визуализации.

ДиспетчерСервлет

Среда Spring Web Model-View-Controller (MVC) разработана на основе DispatcherServlet, который обрабатывает все HTTP-запросы и ответы. Рабочий процесс обработки запросов Spring Web MVC DispatcherServlet показан на следующей диаграмме:

Весенний ДиспетчерСервлет

Ниже приведена последовательность событий, соответствующая входящему HTTP-запросу в DispatcherServlet.

  • После получения HTTP-запроса DispatcherServlet обращается к HandlerMapping для вызова соответствующего контроллера .
  • Контроллер принимает запрос и вызывает соответствующие методы обслуживания на основе используемого метода GET или POST. Сервисный метод устанавливает данные модели на основе определенной бизнес-логики и возвращает имя представления в DispatcherServlet .
  • DispatcherServlet будет получать помощь от ViewResolver для получения определенного представления для запроса.
  • Как только представление завершено, DispatcherServlet передает данные модели в представление, которое в конечном итоге отображается в браузере.

После получения HTTP-запроса DispatcherServlet обращается к HandlerMapping для вызова соответствующего контроллера .

Контроллер принимает запрос и вызывает соответствующие методы обслуживания на основе используемого метода GET или POST. Сервисный метод устанавливает данные модели на основе определенной бизнес-логики и возвращает имя представления в DispatcherServlet .

DispatcherServlet будет получать помощь от ViewResolver для получения определенного представления для запроса.

Как только представление завершено, DispatcherServlet передает данные модели в представление, которое в конечном итоге отображается в браузере.

Все вышеупомянутые компоненты, то есть HandlerMapping, Controller и ViewResolver, являются частями WebApplicationContext w, который является расширением простого ApplicationContext с некоторыми дополнительными функциями, необходимыми для веб-приложений.

Требуемая конфигурация

Вам необходимо сопоставить запросы, которые вы хотите обработать с помощью DispatcherServlet , используя сопоставление URL-адресов в файле web.xml . Ниже приведен пример демонстрации объявления и сопоставления для примера HelloWeb DispatcherServlet:

 id = "WebApp_ID" version = "2.4" xmlns = "http://java.sun.com/xml/ns/j2ee" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> Spring MVC Application   HelloWeb   org.springframework.web.servlet.DispatcherServlet  1    HelloWeb  *.jsp   

Файл web.xml будет храниться в каталоге WebContent / WEB-INF вашего веб-приложения. После инициализации HelloWeb DispatcherServlet платформа попытается загрузить контекст приложения из файла с именем [servlet-name] -servlet.xml, расположенного в каталоге WebContent / WEB-INF приложения. В этом случае наш файл будет HelloWebservlet.xml .

Затем тег указывает, какие URL будут обрабатываться каким DispatcherServlet. Здесь все HTTP-запросы, заканчивающиеся на .jsp, будут обрабатываться HelloWeb DispatcherServlet.

Если вы не хотите использовать имя файла по умолчанию как [servlet-name] -servlet.xml и расположение по умолчанию как WebContent / WEB-INF , вы можете настроить это имя файла и местоположение, добавив прослушиватель сервлета ContextLoaderListener в свой файл web.xml следующим образом –

 DispatcherServlet definition goes here-----> . contextConfigLocation /WEB-INF/HelloWeb-servlet.xml   org.springframework.web.context.ContextLoaderListener   

Теперь давайте проверим необходимую конфигурацию файла HelloWeb-servlet.xml , который находится в каталоге WebContent / WEB-INF вашего веб-приложения –

Ниже приведены важные моменты, касающиеся файла HelloWeb-servlet.xml.

  • Файл [servlet-name] -servlet.xml будет использоваться для создания определенных bean-компонентов, переопределяя определения любых bean-компонентов, определенных с таким же именем в глобальной области видимости.
  • Тег будет использоваться для активации возможности сканирования аннотаций Spring MVC, которая позволяет использовать аннотации, такие как @Controller, @RequestMapping и т. Д.
  • InternalResourceViewResolver будет иметь правила, определенные для разрешения имен представлений. Согласно определенному выше правилу, логическое представление с именем hello делегируется реализации представления, расположенной в /WEB-INF/jsp/hello.jsp .

Файл [servlet-name] -servlet.xml будет использоваться для создания определенных bean-компонентов, переопределяя определения любых bean-компонентов, определенных с таким же именем в глобальной области видимости.

Тег будет использоваться для активации возможности сканирования аннотаций Spring MVC, которая позволяет использовать аннотации, такие как @Controller, @RequestMapping и т. Д.

InternalResourceViewResolver будет иметь правила, определенные для разрешения имен представлений. Согласно определенному выше правилу, логическое представление с именем hello делегируется реализации представления, расположенной в /WEB-INF/jsp/hello.jsp .

Следующий раздел покажет вам, как создать ваши фактические компоненты, например, Controller, Model и View.

Определение контроллера

DispatcherServlet делегирует запрос контроллерам для выполнения специфической для него функциональности. Аннотация @Controller указывает, что определенный класс выполняет роль контроллера. Аннотация @RequestMapping используется для сопоставления URL либо с целым классом, либо с конкретным методом-обработчиком.

@Controller @RequestMapping("/hello") public class HelloController < @RequestMapping(method = RequestMethod.GET) public String printHello(ModelMap model) < model.addAttribute("message", "Hello Spring MVC Framework!"); return "hello"; >>

Аннотация @Controller определяет класс как контроллер Spring MVC. Здесь первое использование @RequestMapping указывает, что все методы обработки на этом контроллере относятся к пути / hello . Следующая аннотация @RequestMapping (method = RequestMethod.GET) используется для объявления метода printHello () в качестве метода службы контроллера по умолчанию для обработки HTTP-запроса GET. Вы можете определить другой метод для обработки любого запроса POST по тому же URL.

Вы можете написать приведенный выше контроллер в другой форме, где вы можете добавить дополнительные атрибуты в @RequestMapping следующим образом:

@Controller public class HelloController < @RequestMapping(value = "/hello", method = RequestMethod.GET) public String printHello(ModelMap model) < model.addAttribute("message", "Hello Spring MVC Framework!"); return "hello"; >>

Атрибут value указывает URL-адрес, с которым сопоставляется метод обработчика, а атрибут метода определяет метод службы для обработки HTTP-запроса GET. О контроллере, определенном выше, следует отметить следующие важные моменты:

  • Вы определите необходимую бизнес-логику внутри метода сервиса. Вы можете вызвать другой метод внутри этого метода согласно требованию.
  • На основе определенной бизнес-логики вы создадите модель в этом методе. Вы можете использовать различные атрибуты модели сеттера, и эти атрибуты будут доступны представлению для представления окончательного результата. В этом примере создается модель с атрибутом «сообщение».
  • Определенный метод службы может возвращать строку, которая содержит имя представления, которое будет использоваться для визуализации модели. Этот пример возвращает “привет” как логическое имя представления.

Вы определите необходимую бизнес-логику внутри метода сервиса. Вы можете вызвать другой метод внутри этого метода согласно требованию.

На основе определенной бизнес-логики вы создадите модель в этом методе. Вы можете использовать различные атрибуты модели сеттера, и эти атрибуты будут доступны представлению для представления окончательного результата. В этом примере создается модель с атрибутом «сообщение».

Определенный метод службы может возвращать строку, которая содержит имя представления, которое будет использоваться для визуализации модели. Этот пример возвращает “привет” как логическое имя представления.

Создание JSP-видов

Spring MVC поддерживает много типов представлений для различных технологий представления. К ним относятся – JSP, HTML, PDF, рабочие листы Excel, XML, шаблоны Velocity, XSLT, JSON, Atom и RSS-каналы, JasperReports и т. Д. Но чаще всего мы используем шаблоны JSP, написанные с использованием JSTL.

Давайте напишем простое приветственное представление в /WEB-INF/hello/hello.jsp –

  Hello Spring MVC  $  

Здесь $ – это атрибут, который мы установили внутри контроллера. Вы можете иметь несколько атрибутов для отображения внутри вашего представления.

Spring Web MVC Framework Примеры

Основываясь на вышеизложенных концепциях, давайте проверим несколько важных примеров, которые помогут вам в создании веб-приложений Spring –

В этом примере объясняется, как написать простое приложение Spring Web Hello World.

В этом примере объясняется, как написать веб-приложение Spring с использованием HTML-форм для отправки данных в контроллер и отображения обработанного результата.

Узнайте, как использовать функциональность перенаправления страниц в Spring MVC Framework.

Узнайте, как получить доступ к статическим страницам вместе с динамическими страницами в Spring MVC Framework.

Узнайте, как обрабатывать исключения в Spring MVC Framework.

Доступ HttpContext в ASP.NET Core

HttpContext инкапсулирует все сведения о отдельном HTTP-запросе и ответе. Экземпляр HttpContext инициализируется при получении HTTP-запроса. Экземпляр HttpContext доступен по промежуточному слоям и платформам приложений, таким как контроллеры веб-API, Razor Pages, SignalRgRPC и многое другое.

Сведения об использовании HttpContext с HTTP-запросом и ответом см. в разделе «Использование HttpContext» в ASP.NET Core.

Доступ HttpContext из Razor страниц

Класс Razor Pages PageModel предоставляет свойство PageModel.HttpContext.

public class IndexModel : PageModel < public void OnGet() < var message = HttpContext.Request.PathBase; // . >> 

То же свойство можно использовать в соответствующем представлении страницы Razor.

@page @model IndexModel @ < var message = HttpContext.Request.PathBase; // . >

Доступ HttpContext из Razor представления в MVC

Представления Razor в шаблоне MVC предоставляют HttpContext через свойство RazorPage.Context в представлении. В следующем примере имя текущего пользователя в приложении интрасети извлекается с использованием проверки подлинности Windows:

Доступ HttpContext с контроллера

Контроллеры предоставляют свойство ControllerBase.HttpContext.

public class HomeController : Controller < public IActionResult About() < var pathBase = HttpContext.Request.PathBase; // . return View(); >> 

Доступ HttpContext из минимальных API

Для использования HttpContext через минимальные API, добавьте параметр HttpContext :

app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World")); 

Доступ HttpContext из ПО промежуточного слоя

Для использования HttpContext из пользовательского ПО промежуточного слоя используйте параметр HttpContext , передаваемый в метод Invoke или InvokeAsync :

public class MyCustomMiddleware < // . public async Task InvokeAsync(HttpContext context) < // . >> 

Доступ HttpContext из SignalR

Чтобы использовать HttpContext из SignalR, вызовите метод GetHttpContext для Hub.Context:

public class MyHub : Hub < public async Task SendMessage() < var httpContext = Context.GetHttpContext(); // . >> 

Доступ HttpContext из методов gRPC

Сведения об использовании HttpContext из методов gRPC см. в разделе «Разрешение HttpContext » в методах gRPC.

Доступ HttpContext из пользовательских компонентов

Для других компонентов платформы и пользовательских компонентов, которым требуется доступ к HttpContext , рекомендуется зарегистрировать зависимость с помощью встроенного контейнера внедрения зависимостей (DI). Контейнер DI предоставляет IHttpContextAccessor для всех классов для объявления в качестве зависимости в своих конструкторах.

var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); builder.Services.AddHttpContextAccessor(); builder.Services.AddTransient(); 

В следующем примере :

  • UserRepository объявляет зависимость от IHttpContextAccessor .
  • Зависимость предоставляется, если DI разрешает цепочку зависимостей и создает экземпляр UserRepository .
public class UserRepository : IUserRepository < private readonly IHttpContextAccessor _httpContextAccessor; public UserRepository(IHttpContextAccessor httpContextAccessor) =>_httpContextAccessor = httpContextAccessor; public void LogCurrentUser() < var username = _httpContextAccessor.HttpContext.User.Identity.Name; // . >> 

HttpContext доступ из фонового потока

HttpContext не является потокобезопасным. Чтение или запись свойств HttpContext за пределами обработки запроса может привести к NullReferenceException.

Если приложение генерирует случайные ошибки NullReferenceException , просмотрите части кода, которые запускают фоновую обработку или продолжают обработку после выполнения запроса. Ищите ошибки, например, как определение метода контроллера в качестве async void .

Для безопасного выполнения фоновой работы с данными HttpContext :

  • Скопируйте необходимые данные во время обработки запроса.
  • Передайте скопированные данные в фоновую задачу.
  • Не ссылайтесь на данные HttpContext в параллельных задачах. Перед запуском параллельных задач извлеките необходимые данные из контекста.

Чтобы избежать небезопасного кода, никогда не передавайте HttpContext в метод, который выполняет фоновую работу. Вместо этого передайте нужные данные. В приведенном ниже примере SendEmail вызывает SendEmailCoreAsync для запуска отправки сообщения электронной почты. Значение заголовка X-Correlation-Id передается в SendEmailCoreAsync , а не в HttpContext . Код не ждет завершения выполнения метода SendEmailCoreAsync :

public class EmailController : Controller < public IActionResult SendEmail(string email) < var correlationId = HttpContext.Request.Headers["X-Correlation-Id"].ToString(); _ = SendEmailCoreAsync(correlationId); return View(); >private async Task SendEmailCoreAsync(string correlationId) < // . >> 

IHttpContextAccessor / HttpContext в Razor компонентах (Blazor)

IHttpContextAccessor необходимо избежать интерактивной отрисовки, так как не существует допустимой HttpContext возможности.

IHttpContextAccessor можно использовать для компонентов, которые статически отрисовываются на сервере. Тем не менее, мы рекомендуем избегать его, если это возможно.

HttpContext можно использовать в качестве каскадного параметра только в статически отрисованных корневых компонентах для общих задач, таких как проверка и изменение заголовков или других свойств компонента App ( Components/App.razor ). Значение всегда null предназначено для интерактивной отрисовки.

[CascadingParameter] public HttpContext? HttpContext

В сценариях, где HttpContext требуется в интерактивных компонентах, рекомендуется передавать данные через постоянное состояние компонента с сервера. Дополнительные сведения см . на стороне сервера ASP.NET Основных Blazor дополнительных сценариев безопасности.

Не используйте IHttpContextAccessor/HttpContext непосредственно или косвенно в Razor компонентах серверных Blazor приложений. Blazor приложения выполняются вне контекста конвейера ASP.NET Core. Он HttpContext не гарантируется, что он доступен в пределах приложения IHttpContextAccessorи HttpContext не гарантируется, что он будет содержать контекст, который запустил Blazor приложение.

Рекомендуемый подход для передачи состояния Blazor запроса приложению осуществляется через параметры корневого компонента во время первоначальной отрисовки приложения. Кроме того, приложение может скопировать данные в область службу в событии жизненного цикла инициализации корневого компонента для использования в приложении. Дополнительные сведения см . на стороне сервера ASP.NET Основных Blazor дополнительных сценариев безопасности.

Критически важный аспект безопасности на стороне Blazor сервера заключается в том, что пользователь, подключенный к заданному каналу, может быть обновлен в какой-то момент после Blazor установки канала, но IHttpContextAccessorне обновляется. Дополнительные сведения об устранении этой ситуации с пользовательскими службами см. в дополнительных сценариях безопасности на стороне сервера ASP.NET CoreBlazor.

HttpContext инкапсулирует все сведения о отдельном HTTP-запросе и ответе. Экземпляр HttpContext инициализируется при получении HTTP-запроса. Экземпляр HttpContext доступен по промежуточному слоям и платформам приложений, таким как контроллеры веб-API, Razor Pages, SignalRgRPC и многое другое.

Сведения об использовании HttpContext с HTTP-запросом и ответом см. в разделе «Использование HttpContext» в ASP.NET Core.

Доступ HttpContext из Razor страниц

Класс Razor Pages PageModel предоставляет свойство PageModel.HttpContext.

public class IndexModel : PageModel < public void OnGet() < var message = HttpContext.Request.PathBase; // . >> 

То же свойство можно использовать в соответствующем представлении страницы Razor.

@page @model IndexModel @ < var message = HttpContext.Request.PathBase; // . >

Доступ HttpContext из Razor представления в MVC

Представления Razor в шаблоне MVC предоставляют HttpContext через свойство RazorPage.Context в представлении. В следующем примере имя текущего пользователя в приложении интрасети извлекается с использованием проверки подлинности Windows:

Доступ HttpContext с контроллера

Контроллеры предоставляют свойство ControllerBase.HttpContext.

public class HomeController : Controller < public IActionResult About() < var pathBase = HttpContext.Request.PathBase; // . return View(); >> 

Доступ HttpContext из ПО промежуточного слоя

При работе с компонентами пользовательского ПО промежуточного слоя HttpContext передается в метод Invoke или InvokeAsync .

public class MyCustomMiddleware < public Task InvokeAsync(HttpContext context) < // . >> 

Доступ HttpContext из пользовательских компонентов

Для других компонентов платформы и пользовательских компонентов, которым требуется доступ к HttpContext , рекомендуется зарегистрировать зависимость с помощью встроенного контейнера внедрения зависимостей (DI). Контейнер DI предоставляет IHttpContextAccessor для всех классов для объявления в качестве зависимости в своих конструкторах.

public void ConfigureServices(IServiceCollection services) < services.AddControllersWithViews(); services.AddHttpContextAccessor(); services.AddTransient(); > 

В следующем примере :

  • UserRepository объявляет зависимость от IHttpContextAccessor .
  • Зависимость предоставляется, если DI разрешает цепочку зависимостей и создает экземпляр UserRepository .
public class UserRepository : IUserRepository < private readonly IHttpContextAccessor _httpContextAccessor; public UserRepository(IHttpContextAccessor httpContextAccessor) < _httpContextAccessor = httpContextAccessor; >public void LogCurrentUser() < var username = _httpContextAccessor.HttpContext.User.Identity.Name; service.LogAccessRequest(username); >> 

HttpContext доступ из фонового потока

HttpContext не является потокобезопасным. Чтение или запись свойств HttpContext за пределами обработки запроса может привести к NullReferenceException.

Если приложение генерирует случайные ошибки NullReferenceException , просмотрите части кода, которые запускают фоновую обработку или продолжают обработку после выполнения запроса. Ищите ошибки, например, как определение метода контроллера в качестве async void .

Для безопасного выполнения фоновой работы с данными HttpContext :

  • Скопируйте необходимые данные во время обработки запроса.
  • Передайте скопированные данные в фоновую задачу.
  • Не ссылайтесь на данные HttpContext в параллельных задачах. Перед запуском параллельных задач извлеките необходимые данные из контекста.

Чтобы избежать небезопасного кода, никогда не передавайте HttpContext в метод, который выполняет фоновую работу. Вместо этого передайте нужные данные. В приведенном ниже примере метод SendEmailCore вызывается для запуска отправки сообщения электронной почты. correlationId передается в SendEmailCore , а не в HttpContext . Код не ждет завершения выполнения метода SendEmailCore :

public class EmailController : Controller < public IActionResult SendEmail(string email) < var correlationId = HttpContext.Request.Headers["x-correlation-id"].ToString(); _ = SendEmailCore(correlationId); return View(); >private async Task SendEmailCore(string correlationId) < // . >> 

IHttpContextAccessor / HttpContext в Razor компонентах (Blazor)

IHttpContextAccessor необходимо избежать интерактивной отрисовки, так как не существует допустимой HttpContext возможности.

IHttpContextAccessor можно использовать для компонентов, которые статически отрисовываются на сервере. Тем не менее, мы рекомендуем избегать его, если это возможно.

HttpContext можно использовать в качестве каскадного параметра только в статически отрисованных корневых компонентах для общих задач, таких как проверка и изменение заголовков или других свойств компонента App ( Components/App.razor ). Значение всегда null предназначено для интерактивной отрисовки.

[CascadingParameter] public HttpContext? HttpContext

В сценариях, где HttpContext требуется в интерактивных компонентах, рекомендуется передавать данные через постоянное состояние компонента с сервера. Дополнительные сведения см . на стороне сервера ASP.NET Основных Blazor дополнительных сценариев безопасности.

Не используйте IHttpContextAccessor/HttpContext непосредственно или косвенно в Razor компонентах серверных Blazor приложений. Blazor приложения выполняются вне контекста конвейера ASP.NET Core. Он HttpContext не гарантируется, что он доступен в пределах приложения IHttpContextAccessorи HttpContext не гарантируется, что он будет содержать контекст, который запустил Blazor приложение.

Рекомендуемый подход для передачи состояния Blazor запроса приложению осуществляется через параметры корневого компонента во время первоначальной отрисовки приложения. Кроме того, приложение может скопировать данные в область службу в событии жизненного цикла инициализации корневого компонента для использования в приложении. Дополнительные сведения см . на стороне сервера ASP.NET Основных Blazor дополнительных сценариев безопасности.

Критически важный аспект безопасности на стороне Blazor сервера заключается в том, что пользователь, подключенный к заданному каналу, может быть обновлен в какой-то момент после Blazor установки канала, но IHttpContextAccessorне обновляется. Дополнительные сведения об устранении этой ситуации с пользовательскими службами см. в дополнительных сценариях безопасности на стороне сервера ASP.NET CoreBlazor.

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

Spring MVC — основные принципы

Фреймворк Spring MVC обеспечивает архитектуру паттерна Model — View — Controller (Модель — Отображение (далее — Вид) — Контроллер) при помощи слабо связанных готовых компонентов. Паттерн MVC разделяет аспекты приложения (логику ввода, бизнес-логику и логику UI), обеспечивая при этом свободную связь между ними.

  • Model (Модель) инкапсулирует (объединяет) данные приложения, в целом они будут состоять из POJO («Старых добрых Java-объектов», или бинов).
  • View (Отображение, Вид) отвечает за отображение данных Модели, — как правило, генерируя HTML, которые мы видим в своём браузере.
  • Controller (Контроллер) обрабатывает запрос пользователя, создаёт соответствующую Модель и передаёт её для отображения в Вид.

DispatcherServlet

Вся логика работы Spring MVC построена вокруг DispatcherServlet, который принимает и обрабатывает все HTTP-запросы (из UI) и ответы на них. Рабочий процесс обработки запроса DispatcherServlet’ом проиллюстрирован на следующей диаграмме:

Ниже приведена последовательность событий, соответствующая входящему HTTP-запросу:

  • После получения HTTP-запроса DispatcherServlet обращается к интерфейсу HandlerMapping, который определяет, какой Контроллер должен быть вызван, после чего, отправляет запрос в нужный Контроллер.
  • Контроллер принимает запрос и вызывает соответствующий служебный метод, основанный на GET или POST. Вызванный метод определяет данные Модели, основанные на определённой бизнес-логике и возвращает в DispatcherServlet имя Вида (View).
  • При помощи интерфейса ViewResolver DispatcherServlet определяет, какой Вид нужно использовать на основании полученного имени.
  • После того, как Вид (View) создан, DispatcherServlet отправляет данные Модели в виде атрибутов в Вид, который в конечном итоге отображается в браузере.

Конфигурирование

Вам будет необходимо связать (замапить) запросы, которые Вы хотите обработать при помощи DispatcherServlet, используя мапинг URL в файле web.xml. Ниже приведён пример объявления и мапинга DispatcherServlet’а HelloWeb:

 Spring MVC Application HelloWeb org.springframework.web.servlet.DispatcherServlet 1  HelloWeb *.jsp  

Файл web.xml будет находиться в каталоге WebContent/WEB-INF. После инициализации HelloWeb, фреймворк попытается загрузить контекст приложения из файла с именем [servlet-name]-servlet.xml, находящегося в каталоге WebContent/WEB-INF. В нашем случае, это будет HelloWeb-servlet.xml.

Далее, тэг указывает, какие веб-адреса обрабатываются каким DispatcherServlet’ом. В нашем случае, все HTTP-запросы, заканчивающиеся на «.jsp», будут обработаны HelloWeb.

Если Вы не хотите использовать [servlet-name]-servlet.xml / WebContent/WEB-INF в качестве файла и директории по умолчанию, Вы можете настроить имя файла и директорию, добавив слушатель сервлета (servlet listener) ContextLoaderListener в web.xml, как показано ниже:

  . contextConfigLocation /WEB-INF/HelloWeb-servlet.xml   org.springframework.web.context.ContextLoaderListener  

Теперь давайте проверим конфигурацию для HelloWeb-servlet.xml, размещённую в каталоге WebContent/WEB-INF:

Ниже приведены важные моменты в HelloWeb-servlet.xml:

  • Файл [servlet-name]-servlet.xml будет использоваться для создания обозначенных бинов, с переопределением определений каждого бина, определённых с тем же именем в глобальной области.
  • Тэг будет использован для для активации функции сканирования аннотаций Spring MVC, позволяющей использовать аннотации, такие как Controller, @RequestMapping и проч.
  • Имена Видов (View) будут определяться при помощи InternalResourceViewResolver. В соответствии с указанным выше правилом, Вид с именем hello будет реализован по адресу /WEB-INF/jsp/hello.jsp

Определение Контроллера

DispatcherServlet отправляет запрос контроллерам для выполнения определённых функций. Аннотация @Controllerannotation указывает, что конкретный класс является контроллером. Аннотация @RequestMapping используется для мапинга (связывания) с URL для всего класса или для конкретного метода обработчика.

@Controller @RequestMapping("/hello") public class HelloController < @RequestMapping(method = RequestMethod.GET) public String printHello(ModelMap model) < model.addAttribute("message", "Hello Spring MVC Framework!"); return "hello"; >>

Аннотация Controller определяет класс как Контроллер Spring MVC. В первом случае, @RequestMapping указывает, что все методы в данном Контроллере относятся к URL-адресу «/hello». Следующая аннотация @RequestMapping(method = RequestMethod.GET) используется для объявления метода printHello() как дефолтного метода для обработки HTTP-запросов GET (в данном Контроллере). Вы можете определить любой другой метод как обработчик всех POST-запросов по данному URL-адресу.

Вы можете написать вышеуказанный Контроллер по-другому, указав дополнительные атрибуты для аннотации @RequestMapping следующим образом:

@Controller public class HelloController < @RequestMapping(value = "/hello", method = RequestMethod.GET) public String printHello(ModelMap model) < model.addAttribute("message", "Hello Spring MVC Framework!"); return "hello"; >>

Атрибут «value» указывает URL, с которым мы связываем данный метод (value = «/hello»), далее указывается, что этот метод будет обрабатывать GET-запросы (method = RequestMethod.GET). Также, нужно отметить важные моменты в отношении приведённого выше контроллера:

  • Вы определяете бизнес-логику внутри связанного таким образом служебного метода. Из него Вы можете вызывать любые другие методы.
  • Основываясь на заданной бизнес-логике, в рамках этого метода Вы создаёте Модель (Model). Вы можете добавлять аттрибуты Модели, которые будут добавлены в Вид (View). В примере выше мы создаём Модель с атрибутом «message».
  • Данный служебный метод возвращает имя Вида в виде строки String. В данном случае, запрашиваемый Вид имеет имя «hello».

Создание Вида (JSP)

Spring MVC поддерживает множество типов Видов для различных технологий отображения страницы. В том числе — JSP, HTML, PDF, Excel, XML, Velocity templates, XSLT, JSON, каналы Atom и RSS, JasperReports и проч. Но чаще всего используются шаблоны JSP, написанные при помощи JSTL.

Давайте напишем простой Вид «hello» в /WEB-INF/hello/hello.jsp:

  Hello Spring MVC  $ 

В данном случае, переменная $ выводит тот самый атрибут, установленный нами в Контроллере. Внутри Вида Вы можете отобразить любое количество атрибутов.

Примеры реализации фреймворка Spring MVC

Основываясь на приведённых выше концепциях, предлагаю выполнить несколько важных уроков, которые в дальнейшем помогут нам создавать приложения Spring Web:

Spring MVC Hello World Example
Пример, разъясняющий написание простейшего приложения Hello World.

Spring MVC Form Handling Example
В этом примере объясняется, как написать приложение Spring Web с помощью форм HTML, отправить данные контроллеру и отобразить обработанный результат.

Spring Static Pages Example
Получаем доступ к статическим страницам вместе с динамическими.

Spring изнутри. Этапы инициализации контекста

Доброго времени суток уважаемые хабравчане. Уже 3 года я работаю на проекте в котором мы используем Spring. Мне всегда было интересно разобраться с тем, как он устроен внутри. Я поискал статьи про внутреннее устройство Spring, но, к сожалению, ничего не нашел.

Всех, кого интересует внутреннее устройство Spring, прошу под кат.

На схеме изображены основные этапы поднятия ApplicationContext. В этом посте мы остановимся на каждом из этих этапов. Какой-то этап будет рассмотрен подробно, а какой-то будет описан в общих чертах.

1. Парсирование конфигурации и создание BeanDefinition
  1. Xml конфигурация — ClassPathXmlApplicationContext(“context.xml”)
  2. Конфигурация через аннотации с указанием пакета для сканирования — AnnotationConfigApplicationContext(“package.name”)
  3. Конфигурация через аннотации с указанием класса (или массива классов) помеченного аннотацией @Configuration -AnnotationConfigApplicationContext(JavaConfig.class). Этот способ конфигурации называется — JavaConfig.
  4. Groovy конфигурация — GenericGroovyApplicationContext(“context.groovy”)

Цель первого этапа — это создание всех BeanDefinition. BeanDefinition — это специальный интерфейс, через который можно получить доступ к метаданным будущего бина. В зависимости от того, какая у вас конфигурация, будет использоваться тот или иной механизм парсирования конфигурации.

Xml конфигурация

Для Xml конфигурации используется класс — XmlBeanDefinitionReader, который реализует интерфейс BeanDefinitionReader. Тут все достаточно прозрачно. XmlBeanDefinitionReader получает InputStream и загружает Document через DefaultDocumentLoader. Далее обрабатывается каждый элемент документа и если он является бином, то создается BeanDefinition на основе заполненных данных (id, name, class, alias, init-method, destroy-method и др.). Каждый BeanDefinition помещается в Map. Map хранится в классе DefaultListableBeanFactory. В коде Map выглядит вот так.

/** Map of bean definition objects, keyed by bean name */ private final Map beanDefinitionMap = new ConcurrentHashMap(64); 
Конфигурация через аннотации с указанием пакета для сканирования или JavaConfig

Конфигурация через аннотации с указанием пакета для сканирования или JavaConfig в корне отличается от конфигурации через xml. В обоих случаях используется класс AnnotationConfigApplicationContext.

new AnnotationConfigApplicationContext(JavaConfig.class);
new AnnotationConfigApplicationContext(“package.name”);

Если заглянуть во внутрь AnnotationConfigApplicationContext, то можно увидеть два поля.

private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner; 

ClassPathBeanDefinitionScanner сканирует указанный пакет на наличие классов помеченных аннотацией Component (или любой другой аннотацией которая включает в себя Component). Найденные классы парсируются и для них создаются BeanDefinition.
Чтобы сканирование было запущено, в конфигурации должен быть указан пакет для сканирования.

@ComponentScan()
  1. Первый этап — это регистрация всех @Configuration для дальнейшего парсирования. Если в конфигурации используются Conditional, то будут зарегистрированы только те конфигурации, для которых Condition вернет true. Аннотация Conditional появилась в четвертой версии спринга. Она используется в случае, когда на момент поднятия контекста нужно решить, создавать бин/конфигурацию или нет. Причем решение принимает специальный класс, который обязан реализовать интерфейс Condition.
  2. Второй этап — это регистрация специального BeanFactoryPostProcessor, а именно BeanDefinitionRegistryPostProcessor, который при помощи класса ConfigurationClassParser парсирует JavaConfig и создает BeanDefinition.
Groovy конфигурация

Данная конфигурация очень похожа на конфигурацию через Xml, за исключением того, что в файле не XML, а Groovy. Чтением и парсированием groovy конфигурации занимается класс GroovyBeanDefinitionReader.

2. Настройка созданных BeanDefinition

После первого этапа у нас имеется Map, в котором хранятся BeanDefinition. Архитектура спринга построена таким образом, что у нас есть возможность повлиять на то, какими будут наши бины еще до их фактического создания, иначе говоря мы имеем доступ к метаданным класса. Для этого существует специальный интерфейс BeanFactoryPostProcessor, реализовав который, мы получаем доступ к созданным BeanDefinition и можем их изменять. В этом интерфейсе всего один метод.

public interface BeanFactoryPostProcessor

Метод postProcessBeanFactory принимает параметром ConfigurableListableBeanFactory. Данная фабрика содержит много полезных методов, в том числе getBeanDefinitionNames, через который мы можем получить все BeanDefinitionNames, а уже потом по конкретному имени получить BeanDefinition для дальнейшей обработки метаданных.

Давайте разберем одну из родных реализаций интерфейса BeanFactoryPostProcessor. Обычно, настройки подключения к базе данных выносятся в отдельный property файл, потом при помощи PropertySourcesPlaceholderConfigurer они загружаются и делается inject этих значений в нужное поле. Так как inject делается по ключу, то до создания экземпляра бина нужно заменить этот ключ на само значение из property файла. Эта замена происходит в классе, который реализует интерфейс BeanFactoryPostProcessor. Название этого класса — PropertySourcesPlaceholderConfigurer. Весь этот процесс можно увидеть на рисунке ниже.

Давайте еще раз разберем что же у нас тут происходит. У нас имеется BeanDefinition для класса ClassName. Код класса приведен ниже.

@Component public class ClassName < @Value("$") private String host; @Value("$") private String user; @Value("$") private String password; @Value("$") private Integer port; > 

Если PropertySourcesPlaceholderConfigurer не обработает этот BeanDefinition, то после создания экземпляра ClassName, в поле host проинжектится значение — «$» (в остальные поля проинжектятся соответствующие значения). Если PropertySourcesPlaceholderConfigurer все таки обработает этот BeanDefinition, то после обработки, метаданные этого класса будут выглядеть следующим образом.

@Component public class ClassName

Соответственно в эти поля проинжектятся правильные значения.

Для того что бы PropertySourcesPlaceholderConfigurer был добавлен в цикл настройки созданных BeanDefinition, нужно сделать одно из следующих действий.

Для XML конфигурации.

@Configuration @PropertySource("classpath:property.properties") public class DevConfig < @Bean public static PropertySourcesPlaceholderConfigurer configurer() < return new PropertySourcesPlaceholderConfigurer(); >> 

PropertySourcesPlaceholderConfigurer обязательно должен быть объявлен как static. Без static у вас все будет работать до тех пор, пока вы не попробуете использовать @ Value внутри класса @Configuration.

3. Создание кастомных FactoryBean

FactoryBean — это generic интерфейс, которому можно делегировать процесс создания бинов типа . В те времена, когда конфигурация была исключительно в xml, разработчикам был необходим механизм с помощью которого они бы могли управлять процессом создания бинов. Именно для этого и был сделан этот интерфейс. Для того что бы лучше понять проблему, приведу пример xml конфигурации.

На первый взгляд, тут все нормально и нет никаких проблем. А что делать если нужен другой цвет? Создать еще один бин? Не вопрос.

А что делать если я хочу каждый раз случайный цвет? Вот тут то и приходит на помощь интерфейс FactoryBean.

Создадим фабрику которая будет отвечать за создание всех бинов типа — Color.

package com.malahov.factorybean; import org.springframework.beans.factory.FactoryBean; import org.springframework.stereotype.Component; import java.awt.*; import java.util.Random; /** * User: malahov * Date: 18.04.14 * Time: 15:59 */ public class ColorFactory implements FactoryBean  < @Override public Color getObject() throws Exception < Random random = new Random(); Color color = new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)); return color; >@Override public Class getObjectType() < return Color.class; >@Override public boolean isSingleton() < return false; >> 

Добавим ее в xml и удалим объявленные до этого бины типа — Color.

Теперь создание бина типа Color.class будет делегироваться ColorFactory, у которого при каждом создании нового бина будет вызываться метод getObject.

Для тех кто пользуется JavaConfig, этот интерфейс будет абсолютно бесполезен.

4. Создание экземпляров бинов

Созданием экземпляров бинов занимается BeanFactory при этом, если нужно, делегирует это кастомным FactoryBean. Экземпляры бинов создаются на основе ранее созданных BeanDefinition.

5. Настройка созданных бинов

Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки ваших бинов до того, как они попадут в контейнер. Интерфейс несет в себе несколько методов.

public interface BeanPostProcessor
  1. Оба метода в итоге должны вернуть бин. Если в методе вы вернете null, то при получении этого бина из контекста вы получите null, а поскольку через бинпостпроцессор проходят все бины, после поднятия контекста, при запросе любого бина вы будете получать фиг, в смысле null.
  2. Если вы хотите сделать прокси над вашим объектом, то имейте ввиду, что это принято делать после вызова init метода, иначе говоря это нужно делать в методе postProcessAfterInitialization.

Процесс донастройки показан на рисунке ниже. Порядок в котором будут вызваны BeanPostProcessor не известен, но мы точно знаем что выполнены они будут последовательно.

Для того, что бы лучше понять для чего это нужно, давайте разберемся на каком-нибудь примере.

При разработке больших проектов, как правило, команда делится на несколько групп. Например первая группа разработчиков занимается написанием инфраструктуры проекта, а вторая группа, используя наработки первой группы, занимается написанием бизнес логики. Допустим второй группе понадобился функционал, который позволит в их бины инжектить некоторые значения, например случайные числа.

На первом этапе будет создана аннотация, которой будут помечаться поля класса, в которые нужно проинжектить значение.

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectRandomInt

По умолчанию, диапазон случайных числе будет от 0 до 10.

Затем, нужно создать обработчик этой аннотации, а именно реализацию BeanPostProcessor для обработки аннотации InjectRandomInt.

@Component public class InjectRandomIntBeanPostProcessor implements BeanPostProcessor < private static final Logger LOGGER = LoggerFactory.getLogger(InjectRandomIntBeanPostProcessor.class); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException < LOGGER.info("postProcessBeforeInitialization::beanName = <>, beanClass = <>", beanName, bean.getClass().getSimpleName()); Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) < if (field.isAnnotationPresent(InjectRandomInt.class)) < field.setAccessible(true); InjectRandomInt annotation = field.getAnnotation(InjectRandomInt.class); ReflectionUtils.setField(field, bean, getRandomIntInRange(annotation.min(), annotation.max())); >> return bean; > @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException < return bean; >private int getRandomIntInRange(int min, int max) < return min + (int)(Math.random() * ((max - min) + 1)); >> 

Код данного BeanPostProcessor достаточно прозрачен, поэтому мы не будем на нем останавливаться, но тут есть один важный момент.

BeanPostProcessor обязательно должен быть бином, поэтому мы его либо помечаем аннотацией Component, либо регестрируем его в xml конфигурации как обычный бин.

Первая группа разработчиков свою задачу выполнила. Теперь вторая группа может использовать эти наработки.

Хочу сказать отдельное спасибо EvgenyBorisov. Благодаря его курсу, я решился на написание этого поста.

Также советую посмотреть его доклад с JPoint 2014.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *