O Conceito de Pontos de Extensão
Pontos de extensão funcionam como interfaces estrategicamente posicionadas no framework que permitem acoplar novas funcionalidades sem alterar o núcleo da lógica de negócio. Em vez de criar métodos gigantescos que realizam múltiplas tarefas, utilizamos esses "encaixes" para manter o código modular e fácil de manter.
Abordagem Tradicional vs. Arquitetura Extensível
No modelo tradicional, o código tende a crescer de forma linear e desordenada sempre que surge um novo requisito:
// Abordagem com alto acoplamento
@Service
public class ProcessadorDePedido {
public void finalizarCompra(Pedido pedido) {
validarEstoque(pedido);
salvarNoBanco(pedido);
// Novos requisitos injetados diretamente na lógica principal
notificadorSms.enviar(pedido);
geradorDeNotaFiscal.emitir(pedido);
calculadorDePontos.atribuir(pedido);
}
}
Utilizando pontos de extensão, a lógica principal apenas sinaliza o evento, permitindo que componentes independentes reajam a ele:
// Abordagem extensível e desacoplada
@Service
public class ProcessadorDePedido {
private final ApplicationEventPublisher publicador;
public void finalizarCompra(Pedido pedido) {
validarEstoque(pedido);
salvarNoBanco(pedido);
// Dispara um evento para os interessados
publicador.publishEvent(new PedidoFinalizadoEvent(pedido));
}
}
// Componentes independentes
@Component
public class NotificadorDeVenda {
@EventListener
public void processar(PedidoFinalizadoEvent evento) {
enviarNotificacao(evento.getPedido());
}
}
Extensões no Ciclo de Inicialização
O Spring Boot oferece momentos específicos durante a inicialização para executar rotinas de configuração ou aquecimento de dados.
ApplicationListener para Eventos de Prontidão
O ApplicationReadyEvent é disparado quando a aplicação está totalmente operacional. É ideal para tarefas como o warmup de caches.
@Component
public class InicializadorDeCache implements ApplicationListener<ApplicationReadyEvent> {
private final RepositorioCache cache;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// Lógica de aquecimento de dados críticos
List<String> parametros = carregarConfiguracoesGlobais();
parametros.forEach(p -> cache.armazenar(p.getId(), p));
}
}
CommandLineRunner e ApplicationRunner
Estes pontos permitem capturar argumentos passados via linha de comando no momento da execução do JAR.
@Component
@Order(1)
public class ValidadorDeAmbienteRunner implements CommandLineRunner {
@Override
public void run(String... args) {
if (Arrays.asList(args).contains("--check-db")) {
// Executa verificação de integridade do banco
}
}
}
Manipulação do Ciclo de Vida de Beans
A interface BeanPostProcessor permite interceptar a criação de qualquer objeto gerencaido pelo Spring, possibilitando a criação de proxies ou modificação de atributos em tempo de execução.
@Component
public class MonitoramentoPerformanceProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean.getClass().isAnnotationPresent(MonitorarPerformance.class)) {
return Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy, method, args) -> {
long inicio = System.nanoTime();
Object resultado = method.invoke(bean, args);
System.out.println("Execução de " + method.getName() + ": " + (System.nanoTime() - inicio) + "ns");
return resultado;
}
);
}
return bean;
}
}
Intercepção na Camada Web
Para fluxos que envolvem requisições HTTP, os HandlerInterceptor e Filters são as ferramentas padrão para implementar lógica transversal.
Implementação de um Interceptor de Auditoria
@Component
public class RastreadorRequisicaoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String correlationId = UUID.randomUUID().toString();
request.setAttribute("X-Correlation-ID", correlationId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
String cid = (String) request.getAttribute("X-Correlation-ID");
// Log de finalização com o ID de correlação
}
}
Configuração do Interceptor
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final RastreadorRequisicaoInterceptor rastreador;
public WebMvcConfig(RastreadorRequisicaoInterceptor rastreador) {
this.rastreador = rastreador;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rastreador).addPathPatterns("/api/**");
}
}
Auto-configuração e Condicionais
O poder do Spring Boot reside na capacidade de configurar componentes automaticamente com base no ambiente (existência de classes no classpath ou propriedades específicas).
@Configuration
@ConditionalOnProperty(name = "servico.notificacao.tipo", havingValue = "aws-sns")
public class SNSNotificationAutoConfiguration {
@Bean
public ServicoNotificacao snsProvider() {
return new SnsNotificationService();
}
}
Tratamento Transacional de Eventos
Em sistemas onde a consistência é vital, o TransactionalEventListener garante que uma ação secundária (como enviar um e-mail) ocorra apenas se a transação do banco de dados for confirmada com sucesso.
@Component
public class GestorDeNotificacoesPosVenda {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void dispararEmailConfirmacao(PedidoFinalizadoEvent evento) {
// Este código só executa se o commit no banco for realizado
servicoEmail.enviar(evento.getPedido().getComprador());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void tratarFalhaNoPedido(PedidoFinalizadoEvent evento) {
// Executa lógica de compensação em caso de erro na transação
logger.error("Falha ao processar pedido: " + evento.getPedido().getId());
}
}
Resumo de Aplicabilidade
| Necessidade | Mecanismo Recomendado |
|---|---|
| Desacpolamento de processos de negócio | @EventListener / ApplicationEvent |
| Modificação global de Beans | BeanPostProcessor |
| Segurança e Logging de API | HandlerInterceptor |
| Carga de dados inicial | CommandLineRunner |
| Configuração dinâmica por ambiente | @ConditionalOnProperty |