Uma introdução ao Spring DispatcherServlet

Uma introdução ao Spring DispatcherServlet

1. Introdução

Simplificando, no padrão de designFront Controller,, um único controlador éresponsible for directing incoming HttpRequests to all of an application’s other controllers and handlers.

DispatcherServlet de Spring implementa este padrão e é, portanto, responsável por coordenar corretamente oHttpRequests para seus manipuladores direitos.

Neste artigo, iremosexamine the Spring DispatcherServlet’s request processing workflow e como implementar várias das interfaces que participam deste fluxo de trabalho.

2. DispatcherServlet processamento de solicitação

Essencialmente, umDispatcherServlet handles an incoming HttpRequest, delegates the request, and processes that request according to the configured HandlerAdapter interfaces que foi implementado no aplicativo Spring junto com as anotações que o acompanham especificando manipuladores, terminais de controlador e objetos de resposta.

Vamos nos aprofundar em como aDispatcherServlet processa um componente:

  • oWebApplicationContext associado aDispatcherServlet sob a chaveDispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE é procurado e disponibilizado para todos os elementos do processo

  • ODispatcherServlet encontra todas as implementações da interfaceHandlerAdapter configurada para o seu dispatcher usandogetHandler() – cada implementação encontrada e configurada lida com a solicitação viahandle() durante o restante do processo

  • oLocaleResolver é opcionalmente vinculado à solicitação para permitir que os elementos no processo resolvam a localidade

  • oThemeResolver é opcionalmente vinculado à solicitação para permitir que elementos, como visualizações, determinem qual tema usar

  • se umMultipartResolver for especificado, a solicitação é inspecionada paraMultipartFiles - todos os encontrados são agrupados emMultipartHttpServletRequest para processamento posterior

  • HandlerExceptionResolver implementações declaradas emWebApplicationContext captam exceções que são lançadas durante o processamento da solicitação

Você pode aprender mais sobre todas as maneiras de registrar e configurar umDispatcherServlethere.

3. HandlerAdapter Interfaces

A interfaceHandlerAdapter facilita o uso de controladores, servlets,HttpRequests e caminhos HTTP por meio de várias interfaces específicas. The HandlerAdapter interface thus plays an essential role through the many stages of the DispatcherServlet request processing workflow.

Primeiro, cada implementação deHandlerAdapter é colocada emHandlerExecutionChain a partir do métodogetHandler() do seu despachante. Então, cada uma dessas implementaçõeshandle() o objetoHttpServletRequest conforme a cadeia de execução prossegue.

Nas seções a seguir, exploraremos alguns dosHandlerAdapters mais importantes e comumente usados ​​com mais detalhes.

3.1. Mapeamentos

Para entender os mapeamentos, precisamos primeiro ver como anotar os controladores, já que os controladores são tão essenciais para a interfaceHandlerMapping.

OSimpleControllerHandlerAdapter permite a implementação de um controlador explicitamente sem uma anotação@Controller.

ORequestMappingHandlerAdapter suporta métodos anotados com a anotação@RequestMapping.

Vamos nos concentrar na anotação@Controller aqui, mas um recurso útil com váriosexamples using the SimpleControllerHandlerAdapter também está disponível.

The @RequestMapping annotation sets the specific endpoint at which a handler will be available dentro deWebApplicationContext associados a ele.

Vamos ver um exemplo deController que expõe e trata o endpoint‘/user/example':

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

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

Os caminhos especificados pela anotação@RequestMapping são gerenciados internamente por meio da interfaceHandlerMapping.

A estrutura de URLs é naturalmente relativa ao próprioDispatcherServlet - e determinada pelo mapeamento do servlet.

Assim, seDispatcherServlet for mapeado para '/', todos os mapeamentos serão cobertos por esse mapeamento.

Se, no entanto, o mapeamento do servlet for ‘/dispatcher’, então, quaisquer anotações @RequestMapping serão relativas a esse URL raiz.

Remember that ‘/' is not the same as ‘/ '* para mapeamentos 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. Ele não especifica que todos os caminhos com o mesmo contexto de URL estão sob a área de responsabilidade do expedidor. Em vez disso, ele substitui e ignora os outros mapeamentos do expedidor. Então, ‘/ example 'aparecerá como um 404!

Por esse motivo,‘/ 'não deve ser usado, exceto em circunstâncias muito limitadas * (como configurar um filtro).

3.2. Tratamento de solicitação HTTP

The core responsibility of a DispatcherServlet is to dispatch incoming HttpRequests to the correct handlers especificado com as anotações@Controller ou@RestController.

Como nota lateral, a principal diferença entre@Controllere@RestController é como a resposta é gerada - o@RestController também define@ResponseBody por padrão.

Um artigo em que aprofundamos muito mais os controladores do Spring pode ser encontradohere.

3.3. A interfaceViewResolver

UmViewResolver é anexado aDispatcherServlet como uma definição de configuração em um objetoApplicationContext.

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

Aqui está um exemplo de configuração que colocaremos em nossoAppConfig  para renderizar páginas 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;
    }
}

Muito direto! Existem três partes principais para isso:

  1. definindo o prefixo, que define o caminho da URL padrão para encontrar as visualizações definidas em

  2. o tipo de visualização padrão que é definido através do sufixo

  3. configurar uma classe de visualização no resolvedor que permita que tecnologias como JSTL ou Tiles sejam associadas às visualizações renderizadas

One common question involves how precisely a dispatcher’s ViewResolverand the overall project directory structure are related. Vamos dar uma olhada no básico.

Aqui está um exemplo de configuração de caminho para umInternalViewResolver usando a configuração XML do Spring:

Para o bem do nosso exemplo, vamos supor que nosso aplicativo está sendo hospedado em:

http://localhost:8080/

Este é o endereço e a porta padrão para um servidor Apache Tomcat hospedado localmente.

Supondo que nosso aplicativo seja chamadodispatcherexample-1.0.0, nossas visualizações JSP estarão acessíveis a partir de:

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

O caminho para essas visualizações em um projeto comum do Spring com o Maven é o seguinte:

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

O local padrão para visualizações está dentro de WEB-INF. O caminho especificado para nossoInternalViewResolver no snippet acima determina o subdiretório de ‘src / main / webapp 'no qual suas visualizações estarão disponíveis.

3.4. A interfaceLocaleResolver

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

CookieLocaleResolver é uma implementação que permite a configuração de propriedades de aplicativos sem estado usando cookies. Vamos adicioná-lo aAppConfig.

@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 permite a configuração específica da sessão em um aplicativo com estado.

O métodosetDefaultLocale () representa uma região geográfica, política ou cultural, enquantosetDefaultTimeZone () determines the relevant TimeZone object for the application Bean in question.

Ambos os métodos estão disponíveis em cada uma das implementações acima deLocaleResolver.

3.5. A interfaceThemeResolver

O Spring fornece temas estilísticos para nossos pontos de vista.

Vamos dar uma olhada em como configurar nosso despachante para lidar com temas.

Primeiro,let’s set up all the configuration necessary to find and use our static theme files. Precisamos definir um local de recurso estático para nossoThemeSource para configurar os própriosThemes reais (os objetosTheme contêm todas as informações de configuração estipuladas nesses arquivos). Adicione aAppConfig:

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

As solicitações gerenciadas porDispatcherServlet podem modificar o tema por meio de um parâmetro especificado passado emsetParamName () disponível no objetoThemeChangeInterceptor. Adicionar aAppConfig:

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

A seguinte tag JSP é adicionada à nossa visualização para fazer com que o estilo correto apareça:

A seguinte solicitação de URL renderiza o temaexample usando o parâmetro ‘theme’ passado em nossoThemeChangeIntercepter: configurado

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

3.6. A interfaceMultipartResolver

Uma implementação deMultipartResolver inspeciona uma solicitação de multipartes e os envolve emMultipartHttpServletRequest para processamento posterior por outros elementos no processo se pelo menos uma multiparte for encontrada. Adicionar aAppConfig:

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

Agora que configuramos nosso beanMultipartResolver, vamos configurar um controlador para processar as solicitaçõesMultipartFile:

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

Podemos usar um formulário normal para enviar um arquivo para o terminal especificado. Os arquivos enviados estarão disponíveis em ‘CATALINA_HOME / bin / uploads '.

3.7. A interfaceHandlerExceptionResolver

HandlerExceptionResolver do Spring fornece tratamento de erros uniforme para um aplicativo da web inteiro, um único controlador ou um conjunto de controladores.

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

@ControllerAdvice
public class ExampleGlobalExceptionHandler {

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

Quaisquer métodos dentro dessa classe anotados com@ExceptionHandler estarão disponíveis em cada controlador dentro da área de responsabilidade do despachante.

Implementações da interfaceHandlerExceptionResolver emDispatcherServlet’s ApplicationContext estão disponíveis paraintercept a specific controller sob a área de responsabilidade do despachantewhenever @ExceptionHandler is used as an annotation, e a classe correta é passada como um parâmetro:

@Controller
public class FooController{

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

O métodohandleException() agora servirá como um manipulador de exceção paraFooController em nosso exemplo acima se a exceçãoCustomException1 ouCustomException2 ocorrer.

Here’s um artigo que vai mais a fundo sobre o tratamento de exceções em um aplicativo da web Spring.

4. Conclusão

Neste tutorial, revisamosDispatcherServlet do Spring e várias maneiras de configurá-lo.

Como sempre, o código-fonte usado neste tutorial está disponívelover on Github.