Una introducción al Spring DispatcherServlet

1. Introducción

En pocas palabras, en el patrón de diseño del controlador frontal , un solo controlador es responsable de dirigir las HttpRequests entrantes a todos los demás controladores y controladores de una aplicación .

DispatcherServlet de Spring implementa este patrón y, por lo tanto, es responsable de coordinar correctamente las HttpRequests con sus controladores correctos.

En este artículo, examinaremos el flujo de trabajo de procesamiento de solicitudes de Spring DispatcherServlet y cómo implementar varias de las interfaces que participan en este flujo de trabajo.

2. Procesamiento de solicitudes de DispatcherServlet

Esencialmente, un DispatcherServlet maneja una HttpRequest entrante , delega la solicitud y procesa esa solicitud de acuerdo con las interfaces HandlerAdapter configuradas que se han implementado dentro de la aplicación Spring junto con las anotaciones adjuntas que especifican controladores, puntos finales del controlador y objetos de respuesta.

Profundicemos más en cómo un DispatcherServlet procesa un componente:

  • el WebApplicationContext asociado a un DispatcherServlet bajo la clave DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE se busca y se pone a disposición de todos los elementos del proceso
  • El DispatcherServlet encuentra todas las implementaciones de la HandlerAdapter interfaz configurada para su despachador usando getHandler () - cada uno encontró y manijas de ejecución configurados la solicitud a través de la manija () a través del resto del proceso
  • la LocaleResolver se une opcionalmente a la solicitud para permitir que elementos en el proceso de resolver el locale
  • la ThemeResolver se une opcionalmente a la solicitud de dejar elementos, tales como puntos de vista, determinar qué tema usar
  • si se especifica un MultipartResolver , la solicitud se inspecciona en busca de MultipartFile s; los encontrados se envuelven en un MultipartHttpServletRequest para su posterior procesamiento
  • Las implementaciones de HandlerExceptionResolver declaradas en WebApplicationContext recogen las excepciones que se lanzan durante el procesamiento de la solicitud

Puede obtener más información sobre todas las formas de registrarse y configurar un DispatcherServlet aquí.

3. Interfaces HandlerAdapter

La interfaz HandlerAdapter facilita el uso de controladores, servlets, HttpRequests y rutas HTTP a través de varias interfaces específicas. La interfaz HandlerAdapter , por tanto, juega un papel esencial a través de las muchas etapas del flujo de trabajo de procesamiento de solicitudes de DispatcherServlet .

Primero, cada implementación de HandlerAdapter se coloca en HandlerExecutionChain desde el método getHandler () de su despachador . Luego, cada una de esas implementaciones maneja () el objeto HttpServletRequest a medida que avanza la cadena de ejecución.

En las siguientes secciones, exploraremos con mayor detalle algunos de los HandlerAdapters más importantes y de uso común .

3.1. Mapeos

Para comprender las asignaciones, primero debemos ver cómo anotar controladores, ya que los controladores son tan esenciales para la interfaz HandlerMapping .

El SimpleControllerHandlerAdapter permite la implementación de un controlador de manera explícita sin @Controller anotación.

Los RequestMappingHandlerAdapter métodos soportes anotados con la @RequestMapping anotación .

Nos centraremos en la anotación @Controller aquí, pero también está disponible un recurso útil con varios ejemplos que utilizan SimpleControllerHandlerAdapter .

La anotación @RequestMapping establece el punto final específico en el que un controlador estará disponible dentro del WebApplicationContext asociado con él.

Veamos un ejemplo de un controlador que expone y maneja el punto final '/ user / example' :

@Controller @RequestMapping("/user") @ResponseBody public class UserController { @GetMapping("/example") public User fetchUserExample() { // ... } }

Las rutas especificadas por la anotación @RequestMapping se administran internamente a través de la interfaz HandlerMapping .

La estructura de las URL es, naturalmente, relativa al propio DispatcherServlet , y está determinada por el mapeo del servlet.

Por lo tanto, si DispatcherServlet se asigna a '/', todas las asignaciones estarán cubiertas por esa asignación.

Sin embargo, si el mapeo de servlet es ' / dispatcher ', entonces cualquier anotación @ RequestMapping será relativa a esa URL raíz.

Recuerde que '/' no es lo mismo que '/ *' para las asignaciones de servlets. '/' es la asignación predeterminada y expone todas las URL al área de responsabilidad del despachador.

'/ *' es confuso para muchos de los desarrolladores de Spring más nuevos. No especifica que todas las rutas con el mismo contexto de URL estén bajo el área de responsabilidad del despachador. En su lugar, anula e ignora las otras asignaciones de despachador. Entonces, '/ example' aparecerá como un 404!

Por esa razón, '/ *' no debe usarse excepto en circunstancias muy limitadas (como configurar un filtro).

3.2. Manejo de solicitudes HTTP

La responsabilidad principal de un DispatcherServlet es enviar HttpRequests entrantes a los controladores correctos especificados con las anotaciones @Controller o @RestController .

Como nota al margen, la principal diferencia entre @Controller y @RestController es cómo se genera la respuesta: @RestController también define @ResponseBody de forma predeterminada.

Aquí se puede encontrar un artículo en el que profundizamos mucho más sobre los controladores de Spring.

3.3. La interfaz ViewResolver

Un ViewResolver se adjunta a un DispatcherServlet como una opción de configuración en un objeto ApplicationContext .

Un ViewResolver determina tanto el tipo de vistas que proporciona el despachador como desde dónde se sirven .

Aquí hay una configuración de ejemplo que colocaremos en nuestro AppConfig para representar páginas 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; } }

¡Muy sencillo! Hay tres partes principales para esto:

  1. establecer el prefijo, que establece la ruta URL predeterminada para encontrar las vistas establecidas dentro
  2. el tipo de vista predeterminado que se establece mediante el sufijo
  3. establecer una clase de vista en el resolutor que permite que tecnologías como JSTL o Tiles se asocien con las vistas renderizadas

Una pregunta común tiene que ver con la precisión con la que se relacionan el ViewResolver de un despachador y la estructura general del directorio del proyecto . Echemos un vistazo a los conceptos básicos.

Aquí hay una configuración de ruta de ejemplo para un InternalViewResolver usando la configuración XML de Spring:

For the sake of our example, we'll assume that our application is being hosted on:

//localhost:8080/

This is the default address and port for a locally hosted Apache Tomcat server.

Assuming that our application is called dispatcherexample-1.0.0, our JSP views will be accessible from:

//localhost:8080/dispatcherexample-1.0.0/jsp/

The path for these views within an ordinary Spring project with Maven is this:

src -| main -| java resources webapp -| jsp WEB-INF

The default location for views is within WEB-INF. The path specified for our InternalViewResolver in the snippet above determines the subdirectory of ‘src/main/webapp' in which your views will be available.

3.4. The LocaleResolver Interface

The primary way to customize session, request, or cookie information for our dispatcher is through the LocaleResolver interface.

CookieLocaleResolver is an implementation allowing the configuration of stateless application properties using cookies. Let's add it to 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 allows for session-specific configuration in a stateful application.

The setDefaultLocale() method represents a geographical, political, or cultural region, whereas setDefaultTimeZone( ) determines the relevant TimeZone object for the application Bean in question.

Both methods are available on each of the above implementations of LocaleResolver.

3.5. The ThemeResolver Interface

Spring provides stylistic theming for our views.

Let's take a look at how to configure our dispatcher to handle themes.

First, let's set up all the configuration necessary to find and use our static theme files. We need to set a static resource location for our ThemeSource to configure the actual Themes themselves (Theme objects contain all of the configuration information stipulated in those files). Add this to 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; } 

Requests managed by the DispatcherServlet can modify the theme through a specified parameter passed into setParamName() available on the ThemeChangeInterceptor object. Add to 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()); } 

The following JSP tag is added to our view to make the correct styling appear:


     

The following URL request renders the example theme using the ‘theme' parameter passed into our configured ThemeChangeIntercepter:

//localhost:8080/dispatcherexample-1.0.0/?theme=example

3.6. The MultipartResolver Interface

A MultipartResolver implementation inspects a request for multiparts and wraps them in a MultipartHttpServletRequest for further processing by other elements in the process if at least one multipart is found. Add to AppConfig:

@Bean public CommonsMultipartResolver multipartResolver() throws IOException { CommonsMultipartResolver resolver = new CommonsMultipartResolver(); resolver.setMaxUploadSize(10000000); return resolver; } 

Now that we've configured our MultipartResolver bean, let's set up a controller to process MultipartFile requests:

@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; } }

We can use a normal form to submit a file to the specified endpoint. Uploaded files will be available in ‘CATALINA_HOME/bin/uploads'.

3.7. The HandlerExceptionResolver Interface

Spring's HandlerExceptionResolver provides uniform error handling for an entire web application, a single controller, or a set of controllers.

To provide application-wide custom exception handling, create a class annotated with @ControllerAdvice:

@ControllerAdvice public class ExampleGlobalExceptionHandler { @ExceptionHandler @ResponseBody public String handleExampleException(Exception e) { // ... } }

Any methods within that class annotated with @ExceptionHandler will be available on every controller within dispatcher's area of responsibility.

Implementations of the HandlerExceptionResolver interface in the DispatcherServlet's ApplicationContext are available to intercept a specific controller under that dispatcher's area of responsibility whenever @ExceptionHandler is used as an annotation, and the correct class is passed in as a parameter:

@Controller public class FooController{ @ExceptionHandler({ CustomException1.class, CustomException2.class }) public void handleException() { // ... } // ... }

The handleException() method will now serve as an exception handler for FooController in our example above if either exception CustomException1 or CustomException2 occurs.

Here's an article that goes more in-depth about exception handling in a Spring web application.

4. Conclusion

In this tutorial, we've reviewed Spring's DispatcherServlet and several ways to configure it.

As always, the source code used in this tutorial is available over on Github.