Un'introduzione allo Spring DispatcherServlet

1. Introduzione

In poche parole, nel modello di progettazione del controller anteriore , un singolo controller è responsabile di indirizzare le richieste HttpRequests in ingresso a tutti gli altri controller e gestori di un'applicazione .

Il DispatcherServlet di Spring implementa questo modello ed è, pertanto, responsabile del corretto coordinamento delle HttpRequests con i gestori di destra.

In questo articolo, esamineremo il flusso di lavoro di elaborazione delle richieste di Spring DispatcherServlet e come implementare molte delle interfacce che partecipano a questo flusso di lavoro.

2. Elaborazione richiesta DispatcherServlet

In sostanza, un DispatcherServlet gestisce un HttpRequest in arrivo , delega la richiesta ed elabora tale richiesta in base alle interfacce HandlerAdapter configurate che sono state implementate all'interno dell'applicazione Spring insieme alle annotazioni di accompagnamento che specificano gestori, endpoint del controller e oggetti di risposta.

Andiamo più in profondità su come un DispatcherServlet elabora un componente:

  • il WebApplicationContext associato a un DispatcherServlet sotto la chiave DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE viene cercato e reso disponibile a tutti gli elementi del processo
  • Il DispatcherServlet trova tutte le implementazioni dell'interfaccia HandlerAdapter configurata per il tuo dispatcher utilizzando getHandler () - ogni implementazione trovata e configurata gestisce la richiesta tramite handle () attraverso il resto del processo
  • Il LocaleResolver è facoltativamente associato alla richiesta per consentire agli elementi del processo di risolvere la locale
  • la ThemeResolver è opzionalmente legato alla richiesta di lasciare elementi, come vista, determinare quale tema ad uso
  • se viene specificato un MultipartResolver , la richiesta viene ispezionata per MultipartFile s - qualsiasi trovato viene racchiuso in un MultipartHttpServletRequest per un'ulteriore elaborazione
  • Le implementazioni HandlerExceptionResolver dichiarate nel WebApplicationContext raccolgono le eccezioni che vengono generate durante l'elaborazione della richiesta

Puoi saperne di più su tutti i modi per registrarti e impostare un DispatcherServlet qui.

3. Interfacce HandlerAdapter

L' interfaccia HandlerAdapter facilita l'uso di controller, servlet, HttpRequests e percorsi HTTP attraverso diverse interfacce specifiche. L' interfaccia HandlerAdapter svolge quindi un ruolo essenziale durante le numerose fasi del flusso di lavoro di elaborazione delle richieste DispatcherServlet .

Innanzitutto, ogni implementazione di HandlerAdapter viene inserita in HandlerExecutionChain dal metodo getHandler () del tuo dispatcher . Quindi, ciascuna di queste implementazioni gestisce () l' oggetto HttpServletRequest mentre la catena di esecuzione procede.

Nelle sezioni seguenti, esploreremo alcuni degli HandlerAdapter più importanti e comunemente usati in maggiore dettaglio.

3.1. Mappature

Per comprendere le mappature, dobbiamo prima esaminare come annotare i controller poiché i controller sono così essenziali per l' interfaccia HandlerMapping .

Il SimpleControllerHandlerAdapter consente la realizzazione di un controller in modo esplicito senza @Controller annotazione.

I RequestMappingHandlerAdapter metodi supporti annotati con la @RequestMapping annotazioni .

Ci concentreremo sull'annotazione @Controller qui, ma è disponibile anche una risorsa utile con diversi esempi che utilizzano SimpleControllerHandlerAdapter .

L' annotazione @RequestMapping imposta l'endpoint specifico in corrispondenza del quale un gestore sarà disponibile all'interno del WebApplicationContext ad esso associato.

Vediamo un esempio di un controller che espone e gestisce l' endpoint "/ user / example" :

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

I percorsi specificati dall'annotazione @RequestMapping sono gestiti internamente tramite l' interfaccia HandlerMapping .

La struttura degli URL è naturalmente relativa al DispatcherServlet stesso e determinata dalla mappatura del servlet.

Pertanto, se DispatcherServlet è mappato su '/', tutte le mappature saranno coperte da quella mappatura.

Se, tuttavia, la mappatura del servlet è invece " / dispatcher ", tutte le annotazioni @ RequestMapping saranno relative a quell'URL radice.

Ricorda che "/" non è la stessa cosa di "/ *" per le mappature servlet! "/" è la mappatura predefinita ed espone tutti gli URL all'area di responsabilità del committente.

"/ *" crea confusione per molti dei nuovi sviluppatori Spring. Non specifica che tutti i percorsi con lo stesso contesto URL sono sotto l'area di responsabilità del dispatcher. Invece, sovrascrive e ignora le altre mappature del dispatcher. Quindi, "/ example" verrà visualizzato come 404!

Per questo motivo, "/ *" non dovrebbe essere utilizzato tranne in circostanze molto limitate (come la configurazione di un filtro).

3.2. Gestione delle richieste HTTP

La responsabilità principale di un DispatcherServlet è inviare HttpRequests in arrivo ai gestori corretti specificati con le annotazioni @Controller o @RestController .

As a side note, the main difference between @Controller and @RestController is how the response is generated – the @RestController also defines @ResponseBody by default.

A writeup where we go into much greater depth regarding Spring's controllers can be found here.

3.3. The ViewResolver Interface

A ViewResolver is attached to a DispatcherServlet as a configuration setting on an ApplicationContext object.

A ViewResolver determines both what kind of views are served by the dispatcher and from where they are served.

Here's an example configuration which we'll place into our AppConfig for rendering JSP pages:

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

Very straight-forward! There are three main parts to this:

  1. setting the prefix, which sets the default URL path to find the set views within
  2. the default view type which is set via the suffix
  3. setting a view class on the resolver which allows technologies like JSTL or Tiles to be associated with the rendered views

One common question involves how precisely a dispatcher's ViewResolverand the overall project directory structure are related. Let's take a look at the basics.

Here's an example path configuration for an InternalViewResolver using Spring's XML configuration:

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.