Une introduction au Spring DispatcherServlet

Une introduction au Spring DispatcherServlet

1. introduction

En termes simples, dans le modèle de conceptionFront Controller,, un seul contrôleur estresponsible for directing incoming HttpRequests to all of an application’s other controllers and handlers.

LesDispatcherServlet de Spring implémentent ce modèle et sont donc responsables de la coordination correcte desHttpRequests avec leurs gestionnaires droits.

Dans cet article, nous allonsexamine the Spring DispatcherServlet’s request processing workflow et comment implémenter plusieurs des interfaces qui participent à ce flux de travail.

2. Traitement de la demandeDispatcherServlet

Essentiellement, unDispatcherServlet handles an incoming HttpRequest, delegates the request, and processes that request according to the configured HandlerAdapter interfaces qui a été implémenté dans l'application Spring avec les annotations associées spécifiant les gestionnaires, les points de terminaison de contrôleur et les objets de réponse.

Voyons plus en détail comment unDispatcherServlet traite un composant:

  • leWebApplicationContext associé à unDispatcherServlet sous la cléDispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE est recherché et mis à disposition de tous les éléments du processus

  • LeDispatcherServlet trouve toutes les implémentations de l'interfaceHandlerAdapter configurées pour votre répartiteur en utilisantgetHandler() – chaque implémentation trouvée et configurée gère la requête viahandle() pendant le reste du processus

  • leLocaleResolver est éventuellement lié à la demande pour permettre aux éléments du processus de résoudre les paramètres régionaux

  • leThemeResolver est éventuellement lié à la demande de laisser des éléments, tels que des vues, déterminer le thème à utiliser

  • si unMultipartResolver est spécifié, la requête est inspectée pourMultipartFiles - tout trouvé est enveloppé dans unMultipartHttpServletRequest pour un traitement ultérieur

  • Les implémentations deHandlerExceptionResolver déclarées dans les exceptions de collecteWebApplicationContext qui sont levées lors du traitement de la requête

Vous pouvez en savoir plus sur toutes les façons d'enregistrer et de configurer unDispatcherServlethere.

3. InterfacesHandlerAdapter

L'interfaceHandlerAdapter facilite l'utilisation de contrôleurs, de servlets, deHttpRequests et de chemins HTTP via plusieurs interfaces spécifiques. The HandlerAdapter interface thus plays an essential role through the many stages of the DispatcherServlet request processing workflow.

Tout d'abord, chaque implémentation deHandlerAdapter est placée dans lesHandlerExecutionChain de la méthodegetHandler() de votre répartiteur. Ensuite, chacune de ces implémentationshandle()est l'objetHttpServletRequest au fur et à mesure que la chaîne d'exécution avance.

Dans les sections suivantes, nous explorerons plus en détail quelques-uns desHandlerAdaptersles plus importants et les plus couramment utilisés.

3.1. Mappages

Pour comprendre les mappages, nous devons d'abord regarder comment annoter les contrôleurs puisque les contrôleurs sont si essentiels à l'interfaceHandlerMapping.

LeSimpleControllerHandlerAdapter permet la mise en œuvre d'un contrôleur explicitement sans une annotation@Controller.

LeRequestMappingHandlerAdapter prend en charge les méthodes annotées avec l'annotation@RequestMapping.

Nous allons nous concentrer sur l'annotation@Controller ici, mais une ressource utile avec plusieursexamples using the SimpleControllerHandlerAdapter est également disponible.

The @RequestMapping annotation sets the specific endpoint at which a handler will be available dans lesWebApplicationContext qui lui sont associés.

Voyons un exemple deController qui expose et gère le point de terminaison‘/user/example':

@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {

    @GetMapping("/example")
    public User fetchUserExample() {
        // ...
    }
}

Les chemins spécifiés par l'annotation@RequestMapping sont gérés en interne via l'interfaceHandlerMapping.

La structure des URL est naturellement relative auDispatcherServlet lui-même - et déterminée par le mappage de servlet.

Ainsi, si leDispatcherServlet est mappé à «/», alors tous les mappages vont être couverts par ce mappage.

Si, toutefois, le mappage de servlet est «/dispatcher» à la place, toutes les annotations @RequestMapping seront relatives à cette URL racine.

Remember that ‘/' is not the same as ‘/ '* pour les mappages de servlet! ‘/' is the default mapping and exposes all URL’s to the dispatcher’s area of responsibility.

‘/*' is confusing to a lot of newer Spring developers. Il ne spécifie pas que tous les chemins avec le même contexte d'URL relèvent de la zone de responsabilité du répartiteur. Au lieu de cela, il remplace et ignore les autres mappages de répartiteur. Ainsi, ‘/ exemple’ deviendra un 404!

Pour cette raison,‘/ 'ne doit pas être utilisé sauf dans des circonstances très limitées * (comme la configuration d'un filtre).

3.2. Gestion des requêtes HTTP

The core responsibility of a DispatcherServlet is to dispatch incoming HttpRequests to the correct handlers spécifié avec les annotations@Controller ou@RestController.

En passant, la principale différence entre@Controller et@RestController réside dans la manière dont la réponse est générée - le@RestController définit également@ResponseBody par défaut.

Une écriture où nous allons beaucoup plus loin concernant les contrôleurs de Spring peut être trouvéehere.

3.3. L'interfaceViewResolver

UnViewResolver est attaché à unDispatcherServlet en tant que paramètre de configuration sur un objetApplicationContext.

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

Voici un exemple de configuration que nous placerons dans notreAppConfig  pour le rendu des pages JSP:

@Configuration
@EnableWebMvc
@ComponentScan("com.example.springdispatcherservlet")
public class AppConfig implements WebMvcConfigurer {

    @Bean
    public UrlBasedViewResolver viewResolver() {
        UrlBasedViewResolver resolver
          = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
}

Très simple! Il y a trois parties principales à ceci:

  1. définition du préfixe, qui définit le chemin d’URL par défaut pour rechercher les vues définies dans

  2. le type de vue par défaut qui est défini via le suffixe

  3. définition d'une classe de vues sur le résolveur permettant d'associer des technologies telles que JSTL ou Tiles aux vues rendues

One common question involves how precisely a dispatcher’s ViewResolverand the overall project directory structure are related. Jetons un œil aux bases.

Voici un exemple de configuration de chemin pour unInternalViewResolver utilisant la configuration XML de Spring:

Pour les besoins de notre exemple, nous supposerons que notre application est hébergée sur:

http://localhost:8080/

Il s'agit de l'adresse et du port par défaut d'un serveur Apache Tomcat hébergé localement.

En supposant que notre application s'appelledispatcherexample-1.0.0, nos vues JSP seront accessibles depuis:

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

Le chemin pour ces vues dans un projet Spring ordinaire avec Maven est le suivant:

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

L'emplacement par défaut des vues est dans WEB-INF. Le chemin spécifié pour nosInternalViewResolver dans l'extrait ci-dessus détermine le sous-répertoire de «src / main / webapp» dans lequel vos vues seront disponibles.

3.4. L'interfaceLocaleResolver

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

CookieLocaleResolver est une implémentation permettant la configuration de propriétés d'application sans état à l'aide de cookies. Ajoutons-le à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 permet une configuration spécifique à la session dans une application avec état.

La méthodesetDefaultLocale () représente une région géographique, politique ou culturelle, alors quesetDefaultTimeZone () determines the relevant TimeZone object for the application Bean in question.

Les deux méthodes sont disponibles sur chacune des implémentations ci-dessus deLocaleResolver.

3.5. L'interfaceThemeResolver

Spring fournit des thèmes stylistiques pour nos points de vue.

Voyons comment configurer notre répartiteur pour gérer les thèmes.

Tout d'abord,let’s set up all the configuration necessary to find and use our static theme files. Nous devons définir un emplacement de ressource statique pour nosThemeSource afin de configurer eux-mêmes lesThemes réels (les objetsTheme contiennent toutes les informations de configuration stipulées dans ces fichiers). Ajoutez ceci à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;
}

Les requêtes gérées par lesDispatcherServlet peuvent modifier le thème via un paramètre spécifié passé danssetParamName () disponible sur l'objetThemeChangeInterceptor. Ajouter à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());
}

La balise JSP suivante est ajoutée à notre vue pour faire apparaître le style correct:

La requête d'URL suivante restitue le thèmeexample en utilisant le paramètre "theme" passé dans nosThemeChangeIntercepter: configurés

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

3.6. L'interfaceMultipartResolver

Une implémentation deMultipartResolver inspecte une demande de parties multiples et les encapsule dans unMultipartHttpServletRequest pour un traitement ultérieur par d'autres éléments du processus si au moins une partie multiple est trouvée. Ajouter àAppConfig:

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

Maintenant que nous avons configuré notre beanMultipartResolver, configurons un contrôleur pour traiter les requêtesMultipartFile:

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

Nous pouvons utiliser un formulaire normal pour envoyer un fichier au noeud final spécifié. Les fichiers téléchargés seront disponibles dans «CATALINA_HOME / bin / uploads».

3.7. L'interfaceHandlerExceptionResolver

LeHandlerExceptionResolver de Spring fournit une gestion uniforme des erreurs pour une application Web entière, un seul contrôleur ou un ensemble de contrôleurs.

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

@ControllerAdvice
public class ExampleGlobalExceptionHandler {

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

Toutes les méthodes de cette classe annotées avec@ExceptionHandler seront disponibles sur chaque contrôleur dans la zone de responsabilité du répartiteur.

Les implémentations de l'interfaceHandlerExceptionResolver dansDispatcherServlet’s ApplicationContext sont disponibles pourintercept a specific controller sous la zone de responsabilitéwhenever @ExceptionHandler is used as an annotation de ce répartiteur, et la classe correcte est transmise en tant que paramètre:

@Controller
public class FooController{

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

La méthodehandleException() servira désormais de gestionnaire d'exceptions pourFooController dans notre exemple ci-dessus si l'exceptionCustomException1 ouCustomException2 se produit.

Here’s un article qui approfondit la gestion des exceptions dans une application Web Spring.

4. Conclusion

Dans ce didacticiel, nous avons examiné lesDispatcherServlet de Spring et plusieurs façons de le configurer.

Comme toujours, le code source utilisé dans ce tutoriel est disponibleover on Github.