A anotação @ServletComponentScan na inicialização do Spring
1. Visão geral
Neste artigo, examinaremos a nova anotação@ServletComponentScan emSpring Boot.
O objetivo é oferecer suporte às seguintes anotações deServlet 3.0:
-
javax.servlet.annotation.WebFilter
-
javax.servlet.annotation.WebListener
-
javax.servlet.annotation.WebServlet
As classes anotadas@WebServlet,@WebFilter e@WebListener podem ser registradas automaticamente com um contêinerServlet incorporado anotando@ServletComponentScan em uma classe@Configuration especificando os pacotes.
Introduzimos o uso básico de@WebServlet emIntroduction to Java Servletse@WebFilter emIntroduction to Intercepting Filter Pattern in Java. Para@WebListener, você pode dar uma olhada emthis article, que demonstra um caso de uso típico de ouvintes da web.
2. Servlets,Filters eListeners
Antes de mergulhar em@ServletComponentScan, vamos dar uma olhada em como as anotações:@WebServlet,@WebFiltere@WebListener foram usadas antes de@ServletComponentScan entrar em jogo.
2.1. @WebServlet
Agora vamos primeiro definir umServlet que atende às solicitações deGET e responde“hello”:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
try {
response
.getOutputStream()
.write("hello");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2. @WebFilter
Em seguida, um filtro que filtra as solicitações para“/hello” alvo e adiciona“filtering “ à saída:
@WebFilter("/hello")
public class HelloFilter implements Filter {
//...
@Override
public void doFilter(
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
servletResponse
.getOutputStream()
.print("filtering ");
filterChain.doFilter(servletRequest, servletResponse);
}
//...
}
2.3. @WebListener
Por fim, um ouvinte que define um atributo personalizado emServletContext:
@WebListener
public class AttrListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
servletContextEvent
.getServletContext()
.setAttribute("servlet-context-attr", "test");
}
//...
}
2.4. Implantar em um contêinerServlet
Agora que construímos os componentes básicos de um aplicativo da web simples, podemos empacotá-lo e implantá-lo em um contêinerServlet. O comportamento de cada componente pode ser verificado prontamente implantando o arquivo war empacotado emJetty,Tomcat ou quaisquer contêineresServlet que suportamServlet 3.0.
3. Usando@ServletComponentScan emSpring Boot
Você pode se perguntar, já que podemos usar essas anotações na maioria dos contêineresServlet sem nenhuma configuração, por que precisamos de@ServletComponentScan? O problema está nos contêineresServlet incorporados.
Devido ao fato de que os contêineres incorporados não suportam@WebServlet,@WebFiltere@WebListener anotações,Spring Boot, depende muito de contêineres incorporados, introduziu esta nova anotação@ServletComponentScan para suporta alguns jars dependentes que usam essas 3 anotações.
A discussão detalhada pode ser encontrada emthis issue on Github.
3.1. Dependências do Maven
Para usar@ServletComponentScan, precisamos deSpring Boot com a versão 1.3.0 ou superior. Vamos adicionar a versão mais recente despring-boot-starter-parent espring-boot-starter-web aopom:
org.springframework.boot
spring-boot-starter-parent
1.5.1.RELEASE
org.springframework.boot
spring-boot-starter-web
1.5.1.RELEASE
3.2. Usando@ServletComponentScan
O aplicativoSpring Boot é bastante simples. Adicionamos@ServletComponentScan para permitir a verificação de@WebFilter,@WebListener e@WebServlet:
@ServletComponentScan
@SpringBootApplication
public class SpringBootAnnotatedApp {
public static void main(String[] args) {
SpringApplication.run(SpringBootAnnotatedApp.class, args);
}
}
Sem nenhuma alteração no aplicativo Web anterior, ele simplesmente funciona:
@Autowired private TestRestTemplate restTemplate;
@Test
public void givenServletFilter_whenGetHello_thenRequestFiltered() {
ResponseEntity responseEntity =
restTemplate.getForEntity("/hello", String.class);
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
assertEquals("filtering hello", responseEntity.getBody());
}
@Autowired private ServletContext servletContext;
@Test
public void givenServletContext_whenAccessAttrs_thenFoundAttrsPutInServletListner() {
assertNotNull(servletContext);
assertNotNull(servletContext.getAttribute("servlet-context-attr"));
assertEquals("test", servletContext.getAttribute("servlet-context-attr"));
}
3.3. Especificar pacotes a serem verificados
Por padrão,@ServletComponentScan fará a varredura do pacote da classe anotada. Para especificar quais pacotes verificar, podemos usar seus atributos:
-
valor
-
basePackages
-
basePackageClasses
O atributovalue padrão é um alias parabasePackages.
Digamos que nossoSpringBootAnnotatedApp esteja sob o pacotecom.example.annotation, e queremos verificar as classes no pacotecom.example.annotation.components criado no aplicativo da web acima, as seguintes configurações são equivalentes:
@ServletComponentScan
@ServletComponentScan("com.example.annotation.components")
@ServletComponentScan(basePackages = "com.example.annotation.components")
@ServletComponentScan(
basePackageClasses =
{AttrListener.class, HelloFilter.class, HelloServlet.class})
4. Sob o capô
A anotação@ServletComponentScan é processada porServletComponentRegisteringPostProcessor. Depois de escanear pacotes especificados para anotações de@WebFilter,@WebListenere@WebServlet, uma lista deServletComponentHandlers processará seus atributos de anotação e registrará os beans escaneados:
class ServletComponentRegisteringPostProcessor
implements BeanFactoryPostProcessor, ApplicationContextAware {
private static final List HANDLERS;
static {
List handlers = new ArrayList<>();
handlers.add(new WebServletHandler());
handlers.add(new WebFilterHandler());
handlers.add(new WebListenerHandler());
HANDLERS = Collections.unmodifiableList(handlers);
}
//...
private void scanPackage(
ClassPathScanningCandidateComponentProvider componentProvider,
String packageToScan){
//...
for (ServletComponentHandler handler : HANDLERS) {
handler.handle(((ScannedGenericBeanDefinition) candidate),
(BeanDefinitionRegistry) this.applicationContext);
}
}
}
Como dito emofficial Javadoc,@ServletComponentScan annotation only works in embedded Servlet containers, que é o que vem comSpring Boot por padrão.
5. Conclusão
Neste artigo, apresentamos@ServletComponentScan e como ele pode ser usado para oferecer suporte a aplicativos que dependem de qualquer uma das anotações:@WebServlet,@WebFilter,@WebListener.
A implementação dos exemplos e código pode ser encontradain the GitHub project.