Ao desenvolver aplicações web com Spring MVC, um desafio comum surge quando o DispatcherServlet é configurado para interceptar todas as requisições (por exemplo, usando um padrão de URL como /). Nesses casos, os arquivos estáticos, como imagens, folhas de estilo CSS e scripts JavaScript, também são interceptados pelo Spring, resultando em erros 404, pois o DispatcherServlet não encontra nenhum controlador para esses recursos.
Este artigo explora diferentes abordagens para garantir que o Spring MVC possa servir recursos estáticos de forma eficaz, permitindo que a aplicação exiba corretamente seu conteúdo visual e funcional.
- Delegação ao Servlet Padrão do Contêiner via
web.xml
Uma maneira de contornar a interceptação total pelo DispatcherServlet é configurar o web.xml para que o servlet padrão do contêiner (como o Tomcat) lide com tipos de arquivos estáticos antes que as requisições cheguem ao Spring. Esta solução envolve mapear extensões de arquivo específicas para o servlet padrão diretamente no descritor de implantação.
É crucial que esta configuração de mapeamento de servlet para os recursos estáticos seja definida antes da configuração do DispatcherServlet no arquivo web.xml. Dessa forma, o contêiner web processa esses arquivos estáticos primeiro, e as requisições para eles nunca chegam ao Spring MVC.
Exemplo de Configuração web.xml:
<!-- Mapeia extensões de arquivos estáticos para o servlet padrão -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping&
<!-- Configuração do DispatcherServlet do Spring MVC -->
<servlet>
<servlet-name>springMvcDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvcDispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
O nome do servlet padrão pode variar entre os servidroes de aplicação:
- Tomcat, Jetty, JBoss, GlassFish:
default - Google App Engine:
_ah_default - Resin:
resin-file - WebLogic:
FileServlet - WebSphere:
SimpleFileServlet
- Configuração de Recursos Estáticos com
<mvc:resources/>
A partir do Spring 3.0.4, o framework oferece uma maneira mais integrada de gerenciar recursos estáticos através do elemento <mvc:resources/>. Esta abordagem permite que o Spring mapeie explicitamente padrões de URL para localizações de recursos no sistema de arquivos, classpath ou mesmo dentro de JARs.
Para utilizar <mvc:resources/>, é necessário habilitar as configurações do Spring MVC baseadas em anotações com <mvc:annotation-driven/>. Sem esta tag, requisições que normalmente seriam tratadas por controladores podem falhar com 404.
Exemplo de Configuração XML do Spring:
<!-- Habilita o suporte a anotações do Spring MVC -->
<mvc:annotation-driven/>
<!-- Mapeia URLs que começam com /static/ para a pasta /resources/ no projeto -->
<mvc:resources mapping="/static/**" location="/resources/" />
<!-- Outros exemplos: -->
<!-- Mapeia /img/** para /WEB-INF/images/ -->
<mvc:resources mapping="/img/**" location="/WEB-INF/images/" />
<!-- Mapeia /assets/** para a pasta 'assets' no classpath -->
<mvc:resources mapping="/assets/**" location="classpath:/assets/" />
A tag <mvc:resources/> registra um SimpleUrlHandlerMapping que mapeia o padrão mapping (e.g., /static/**) para um ResourceHttpRequestHandler. Este manipulador é responsável por servir os arquivos da location especificada, suportando acesso a recursos em diversos locais, incluindo o classpath e dentro de arquivos JAR.
É importante evitar configurar um defaultHandler para o SimpleUrlHandlerMapping gerado por <mvc:resources/>, pois o ResourceHttpRequestHandler já atua como o manipulador padrão para esses URIs estáticos.
- Delegação ao Servlet Padrão do Contêiner via
<mvc:default-servlet-handler/>
Esta é outra solução fornecida pelo Spring para delegar requisições de recursos estáticos diretamente ao servlet padrão do contêiner. Semelhante a <mvc:resources/>, ela também exige que <mvc:annotation-driven/> esteja configurado.
A principal diferença para a abordagem 1 é que a delegação é orquestrada pelo Spring, em vez de ser uma regra de mapeamento direta no web.xml. O Spring configura um DefaultServletHttpRequestHandler que, por sua vez, repassa a requisição para o servlet padrão do contêiner.
Exemplo de Configuração XML do Spring:
<!-- Habilita o suporte a anotações do Spring MVC -->
<mvc:annotation-driven/>
<!-- Delega requisições de recursos estáticos ao servlet padrão do contêiner -->
<mvc:default-servlet-handler/>
Esta configuração registra um SimpleUrlHandlerMapping para o padrão /**, delegando a responsabilidade de servir os recursos ao org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler. Este manipulador utiliza o servlet padrão do contêiner web para processar a requisição, garantindo que arquivos estáticos sejam servidos corretamente.
Ordem de Execução dos HandlerMappings no Spring
Compreender a ordem de processamento dos HandlerMappings no Spring é fundamental para entender como essas soluções funcionam em conjunto. O Spring processa os manipuladores com base em seus valores de ordem (order), do menor para o maior.
DefaultAnnotationHandlerMapping(utilizado por@Controller):order = 0SimpleUrlHandlerMapping(registrado por<mvc:resources/>):order = 2147483646(um valor alto, mas menor que o próximo)SimpleUrlHandlerMapping(registrado por<mvc:default-servlet-handler/>):order = 2147483647(o maior valor padrão)
Quando uma requisição para um arquivo estático (por exemplo, /images/logo.png) chega ao DispatcherServlet, o Spring tenta encontrar um manipulador na seguinte ordem:
- Primeiro, o
DefaultAnnotationHandlerMappingtenta encontrar um controlador (@Controller) que corresponda a/images/logo.png. Como não existe, a requisição segue em frente. - Em seguida, o
SimpleUrlHandlerMappingconfigurado por<mvc:resources/>(se presente) verifica se o padrão/images/**(ou similar) corresponde à requisição. Se sim, oResourceHttpRequestHandlerserve o arquivo. - Finalmente, se nenhuma das opções anteriores tratou a requisição, o
SimpleUrlHandlerMappingregistrado por<mvc:default-servlet-handler/>intercepta a requisição com seu padrão genérico/**. Este manipulador então delega a requisição ao servlet padrão do contêiner para que ele sirva o recurso estático.
Essa hierarquia de ordem garante que o Spring tente primeiro encontrar um controlador específico e, se não encontrar, gradualmente delega a responsabilidade para mecanismos mais genéricos de tratamento de recursos estáticos.