«1. Введение
Проще говоря, в шаблоне проектирования Front Controller один контроллер отвечает за направление входящих запросов HttpRequest ко всем другим контроллерам и обработчикам приложения.
Spring DispatcherServlet реализует этот шаблон и, следовательно, отвечает за правильную координацию HttpRequests с их правильными обработчиками.
В этой статье мы рассмотрим рабочий процесс обработки запросов Spring DispatcherServlet и способы реализации нескольких интерфейсов, участвующих в этом рабочем процессе.
2. Обработка запроса DispatcherServlet
По сути, DispatcherServlet обрабатывает входящий HttpRequest, делегирует запрос и обрабатывает этот запрос в соответствии с настроенными интерфейсами HandlerAdapter, которые были реализованы в приложении Spring вместе с сопутствующими аннотациями, определяющими обработчики, контроллер конечные точки и объекты ответа.
Давайте более подробно рассмотрим, как DispatcherServlet обрабатывает компонент:
-
WebApplicationContext, связанный с DispatcherServlet под ключом DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, ищется и становится доступным для всех элементов процесса DispatcherServlet находит все реализации интерфейса HandlerAdapter, настроенные для вашего диспетчера с помощью getHandler() — каждая найденная и настроенная реализация обрабатывает запрос через handle() до конца процесса. LocaleResolver необязательно привязан к запросу, чтобы позволить элементам в процессе разрешаться локаль ThemeResolver дополнительно привязывается к запросу, чтобы позволить элементам, таким как представления, определять, какую тему использовать, если указан MultipartResolver, запрос проверяется на наличие MultipartFiles — все найденные объекты помещаются в MultipartHttpServletRequest для дальнейшей обработки. Реализации HandlerExceptionResolver объявленный в выборе WebApplicationContext исключения, возникающие при обработке запроса
Вы можете узнать больше обо всех способах регистрации и настройки DispatcherServlet здесь.
3. Интерфейсы HandlerAdapter
Интерфейс HandlerAdapter упрощает использование контроллеров, сервлетов, запросов HttpRequest и путей HTTP через несколько конкретных интерфейсов. Таким образом, интерфейс HandlerAdapter играет важную роль на многих этапах рабочего процесса обработки запросов DispatcherServlet.
Сначала каждая реализация HandlerAdapter помещается в цепочку HandlerExecutionChain из метода getHandler() вашего диспетчера. Затем каждая из этих реализаций обрабатывает() объект HttpServletRequest по мере выполнения цепочки выполнения.
В следующих разделах мы более подробно рассмотрим несколько наиболее важных и часто используемых HandlerAdapters.
3.1. Сопоставления
Чтобы понять сопоставления, нам нужно сначала посмотреть, как аннотировать контроллеры, поскольку контроллеры очень важны для интерфейса HandlerMapping.
SimpleControllerHandlerAdapter позволяет явно реализовать контроллер без аннотации @Controller.
RequestMappingHandlerAdapter поддерживает методы, аннотированные аннотацией @RequestMapping.
Здесь мы сосредоточимся на аннотации @Controller, но также доступен полезный ресурс с несколькими примерами использования SimpleControllerHandlerAdapter.
Аннотация @RequestMapping устанавливает конкретную конечную точку, в которой обработчик будет доступен в связанном с ним WebApplicationContext.
Давайте рассмотрим пример контроллера, который предоставляет и обрабатывает конечную точку «/user/example»:
@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {
@GetMapping("/example")
public User fetchUserExample() {
// ...
}
}
Пути, указанные в аннотации @RequestMapping, управляются внутри через интерфейс HandlerMapping.
Структура URL-адресов, естественно, связана с самим DispatcherServlet и определяется отображением сервлета.
Таким образом, если DispatcherServlet сопоставлен с «/», то все сопоставления будут покрыты этим сопоставлением.
«Если, однако, вместо этого сопоставление сервлета будет «/dispatcher», то любые аннотации @RequestMapping будут относиться к этому корневому URL-адресу.
Помните, что «/» — это не то же самое, что «/*» для отображений сервлетов! «/» — это сопоставление по умолчанию, и все URL-адреса отображаются в зоне ответственности диспетчера.
‘€˜/*’ сбивает с толку многих новых разработчиков Spring. В нем не указывается, что все пути с одинаковым контекстом URL находятся в зоне ответственности диспетчера. Вместо этого он переопределяет и игнорирует другие сопоставления диспетчера. Таким образом, «/пример» будет отображаться как 404!
По этой причине «/*» не следует использовать, кроме как в очень ограниченных случаях (например, при настройке фильтра).
3.2. Обработка HTTP-запросов
Основной обязанностью DispatcherServlet является отправка входящих HttpRequests правильным обработчикам, указанным в аннотациях @Controller или @RestController.
Кстати, основное различие между @Controller и @RestController заключается в способе генерации ответа — @RestController также определяет @ResponseBody по умолчанию.
Рецензию, в которой мы углубляемся в контроллеры Spring, можно найти здесь.
3.3. Интерфейс ViewResolver
ViewResolver присоединяется к DispatcherServlet как параметр конфигурации объекта ApplicationContext.
ViewResolver определяет, какие представления обслуживаются диспетчером и откуда они обслуживаются.
Вот пример конфигурации, которую мы поместим в наш AppConfig для рендеринга JSP-страниц:
@Configuration
@EnableWebMvc
@ComponentScan("com.baeldung.springdispatcherservlet")
public class AppConfig implements WebMvcConfigurer {
@Bean
public UrlBasedViewResolver viewResolver() {
UrlBasedViewResolver resolver
= new UrlBasedViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
}
Очень просто! Это состоит из трех основных частей:
- setting the prefix, which sets the default URL path to find the set views within
- the default view type which is set via the suffix
- setting a view class on the resolver which allows technologies like JSTL or Tiles to be associated with the rendered views
Один общий вопрос касается того, как точно связаны ViewResolver диспетчера и общая структура каталогов проекта. Давайте взглянем на основы.
Вот пример конфигурации пути для InternalViewResolver с использованием XML-конфигурации Spring:
<property name="prefix" value="/jsp/"/>
Для нашего примера предположим, что наше приложение размещено на:
http://localhost:8080/
Это адрес и порт по умолчанию для локального сервера Apache Tomcat.
Предполагая, что наше приложение называется dispatcherexample-1.0.0, наши представления JSP будут доступны из:
http://localhost:8080/dispatcherexample-1.0.0/jsp/
Путь для этих представлений в обычном проекте Spring с Maven следующий:
src -|
main -|
java
resources
webapp -|
jsp
WEB-INF
Расположение по умолчанию для представлений находится в WEB-INF. Путь, указанный для нашего InternalViewResolver в приведенном выше фрагменте, определяет подкаталог «src/main/webapp», в котором будут доступны ваши представления.
3.4. Интерфейс LocaleResolver
Основной способ настройки сеанса, запроса или информации о файлах cookie для нашего диспетчера — через интерфейс LocaleResolver.
CookieLocaleResolver — это реализация, позволяющая настраивать свойства приложения без сохранения состояния с помощью файлов cookie. Добавим его в AppConfig.
@Bean
public CookieLocaleResolver cookieLocaleResolverExample() {
CookieLocaleResolver localeResolver
= new CookieLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
localeResolver.setCookieName("locale-cookie-resolver-example");
localeResolver.setCookieMaxAge(3600);
return localeResolver;
}
@Bean
public LocaleResolver sessionLocaleResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
localResolver.setDefaultTimeZone(TimeZone.getTimeZone("UTC"));
return localeResolver;
}
SessionLocaleResolver позволяет выполнять настройку сеанса в приложении с отслеживанием состояния.
Метод setDefaultLocale() представляет географический, политический или культурный регион, тогда как setDefaultTimeZone() определяет соответствующий объект TimeZone для рассматриваемого компонента приложения.
Оба метода доступны в каждой из приведенных выше реализаций LocaleResolver.
3.5. Интерфейс ThemeResolver
Spring обеспечивает стилистическое оформление наших представлений.
Давайте посмотрим, как настроить наш диспетчер для обработки тем.
Во-первых, давайте настроим всю конфигурацию, необходимую для поиска и использования наших статических файлов темы. Нам нужно установить местоположение статического ресурса для нашего ThemeSource, чтобы настроить сами фактические темы (объекты темы содержат всю информацию о конфигурации, указанную в этих файлах). Добавьте это в AppConfig:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/", "/resources/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
@Bean
public ResourceBundleThemeSource themeSource() {
ResourceBundleThemeSource themeSource
= new ResourceBundleThemeSource();
themeSource.setDefaultEncoding("UTF-8");
themeSource.setBasenamePrefix("themes.");
return themeSource;
}
Запросы, управляемые DispatcherServlet, могут изменять тему с помощью указанного параметра, переданного в setParamName(), доступного в объекте ThemeChangeInterceptor. Добавить в AppConfig:
@Bean
public CookieThemeResolver themeResolver() {
CookieThemeResolver resolver = new CookieThemeResolver();
resolver.setDefaultThemeName("example");
resolver.setCookieName("example-theme-cookie");
return resolver;
}
@Bean
public ThemeChangeInterceptor themeChangeInterceptor() {
ThemeChangeInterceptor interceptor
= new ThemeChangeInterceptor();
interceptor.setParamName("theme");
return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(themeChangeInterceptor());
}
Следующий тег JSP добавляется в наше представление, чтобы отображался правильный стиль:
<link rel="stylesheet" href="${ctx}/<spring:theme code='styleSheet'/>" type="text/css"/>
«
http://localhost:8080/dispatcherexample-1.0.0/?theme=example
«Следующий URL-запрос отображает пример темы с использованием параметра «theme», переданного в наш настроенный ThemeChangeIntercepter:
3.6. Интерфейс MultipartResolver
@Bean
public CommonsMultipartResolver multipartResolver()
throws IOException {
CommonsMultipartResolver resolver
= new CommonsMultipartResolver();
resolver.setMaxUploadSize(10000000);
return resolver;
}
Реализация MultipartResolver проверяет запрос на составные части и упаковывает их в MultipartHttpServletRequest для дальнейшей обработки другими элементами в процессе, если найдена хотя бы одна составная часть. Добавьте в AppConfig:
@Controller
public class MultipartController {
@Autowired
ServletContext context;
@PostMapping("/upload")
public ModelAndView FileuploadController(
@RequestParam("file") MultipartFile file)
throws IOException {
ModelAndView modelAndView = new ModelAndView("index");
InputStream in = file.getInputStream();
String path = new File(".").getAbsolutePath();
FileOutputStream f = new FileOutputStream(
path.substring(0, path.length()-1)
+ "/uploads/" + file.getOriginalFilename());
int ch;
while ((ch = in.read()) != -1) {
f.write(ch);
}
f.flush();
f.close();
in.close();
modelAndView.getModel()
.put("message", "File uploaded successfully!");
return modelAndView;
}
}
Теперь, когда мы настроили наш bean-компонент MultipartResolver, давайте настроим контроллер для обработки запросов MultipartFile:
Мы можем использовать обычную форму для отправки файла в указанную конечную точку. . Загруженные файлы будут доступны в «CATALINA_HOME/bin/uploads».
3.7. Интерфейс HandlerExceptionResolver
Spring HandlerExceptionResolver обеспечивает единую обработку ошибок для всего веб-приложения, отдельного контроллера или набора контроллеров.
@ControllerAdvice
public class ExampleGlobalExceptionHandler {
@ExceptionHandler
@ResponseBody
public String handleExampleException(Exception e) {
// ...
}
}
Чтобы обеспечить пользовательскую обработку исключений для всего приложения, создайте класс, аннотированный @ControllerAdvice:
Любые методы в этом классе, аннотированные @ExceptionHandler, будут доступны на каждом контроллере в зоне ответственности диспетчера.
@Controller
public class FooController{
@ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
// ...
}
// ...
}
Реализации интерфейса HandlerExceptionResolver в ApplicationContext DispatcherServlet доступны для перехвата конкретного контроллера в зоне ответственности этого диспетчера всякий раз, когда @ExceptionHandler используется в качестве аннотации, а правильный класс передается в качестве параметра:
~~ ~ Метод handleException() теперь будет служить обработчиком исключений для FooController в нашем примере выше, если возникнет исключение CustomException1 или CustomException2.
Вот статья, в которой более подробно рассматривается обработка исключений в веб-приложении Spring.
4. Заключение
В этом руководстве мы рассмотрели DispatcherServlet Spring и несколько способов его настройки.