O mecanismo de callback é um padrão de projeto onde um bloco de código ou uma referência de método é passado como argumento para outro método. O objetivo é permitir que o método receptor execute esse código em um momento específico, geralmente após a conclusão de uma tarefa ou quando um evento ocorre. Essencialmente, trata-se de uma inversão de controle que funciona como um sistema de notificação.
Podemos classificar os callbacks em duas categorias principais:
- Callbacks Síncronos: Ocorrem de forma sequencial. O método que recebe o callback o executa imediatamente dentro do seu fluxo de execução principal, bloqueando o progresso até que o callback termine.
- Callbacks Assíncronos: Comuns em operações de I/O ou chamadas de rede. O callback é acionado apenas quando a tarefa demorada é finalizada, geralmente em uma thread separada, permitindo que o fluxo principal continue sua execução.
1. Estrutura Fundamental
A implementação de um callback geralmente envolve três etapas distintas:
- Registro: O chamador define a lógica de resposta e a envia para a função que executará a tarefa.
- Execução da Tarefa: O método principal realiza o processamento necessário (síncrono ou assíncrono).
- Invocação: Assim que os critérios de finalização são atingidos, o callback é chamado para devolver o resultado ou tratar erros.
2. Implementação em Java
Em Java, as formas mais comuns de implementar callbacks são através de interfaces, classes internas anônimas e, desde o Java 8, expressões Lambda.
2.1. Utilizando Interfaces e Classes Anônimas
Esta é a abordagem clássica, onde definimos um contrato via interface para garantir que o método de retorno exista.
// 1. Definição do contrato de retorno
interface Notificacao {
void aoFinalizar(String mensagem);
}
// 2. Classe que executa a lógica de negócio
class MotorDeProcessamento {
public void processar(Notificacao callback) {
System.out.println("Executando tarefa complexa...");
String status = "Sucesso na Operação";
// Invocação do callback
callback.aoFinalizar(status);
}
}
// 3. Teste do fluxo
public class ExemploCallback {
public static void main(String[] args) {
MotorDeProcessamento motor = new MotorDeProcessamento();
motor.processar(new Notificacao() {
@Override
public void aoFinalizar(String mensagem) {
System.out.println("Feedback recebido: " + mensagem);
}
});
}
}
2.2. Simplificação com Expressões Lambda
Para interfaces funcionais (que possuem apenas um método abstrato), o Java permite uma sintaxe muito mais limpa usando Lambdas.
public class ExemploLambda {
public static void main(String[] args) {
MotorDeProcessamento motor = new MotorDeProcessamento();
// Implementação concisa do callback
motor.processar(res -> System.out.println("Notificação via Lambda: " + res));
}
}
2.3. Callbacks Assíncronos com CompletableFuture
Para cenários onde não queremos bloquear a thread principal, utilizamos ferramentas como CompletableFuture.
import java.util.concurrent.CompletableFuture;
class ServicoRemoto {
public void executarTarefaAssincrona(Notificacao callback) {
CompletableFuture.runAsync(() -> {
try {
// Simula latência de rede
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
callback.aoFinalizar("Tarefa concluída em segundo plano");
});
}
}
public class TesteAssincrono {
public static void main(String[] args) {
ServicoRemoto servico = new ServicoRemoto();
servico.executarTarefaAssincrona(res -> System.out.println("Async Result: " + res));
System.out.println("O fluxo principal continua livre...");
// Mantém a JVM ativa para o exemplo
try { Thread.sleep(2000); } catch (Exception e) {}
}
}
3. Callbacks em Arquitetura Distribuída (Webhooks)
O conceito de callback também se estende para integrações entre sistemas, frequentemente chamados de Webhooks. Um exemplo clássico é a integração com gateways de pagamento. O sistema envia uma requisição de cobrança e fornece uma URL de notificação. Quando o banco processa o pagamento, ele faz uma chamada HTTP POST para essa URL, disparando um callback a nível de sistema.
4. Aplicação no Spring Framework
O Spring utiliza callbacks extensivamente para manter seu núcleo extensível e desacoplado. Um exemplo notável está no BeanFactory. No método doGetBean, ao criar um bean singleton, o Spring utiliza uma interface funcional ObjectFactory.
A lógica funciona da seguinte forma: o método responsável por gerenciar o ciclo de vida do singleton (como controle de cache e instâncias em criação) recebe um callback que contém a lógica real de instanciação (o método createBean). Isso permite que o mecanismo de cache seja genérico, enquanto a criação específica do objeto é injetada via callback.
5. Cenário Prático: Processador Genérico de Dados
Imagine um sistema que precisa converter dados de diferentes fontes (CSV, JSON, Banco de Dados) para um formato interno. Podemos usar callbacks para definir a regra de conversão sem alterar o fluxo de processamento de logs e auditoria.
@FunctionalInterface
interface ConversorDados<T, R> {
R converter(T entrada);
}
class PipelineDados {
public static <T, R> R processarEntrada(T dado, ConversorDados<T, R> conversor) {
System.out.println("Iniciando auditoria de dados...");
R resultado = conversor.converter(dado);
System.out.println("Finalizando registro de logs...");
return resultado;
}
}
public class SistemaLegado {
public static void main(String[] args) {
// Exemplo: Convertendo uma String bruta para um objeto de domínio
String dadoBruto = "ID:123;NOME:Java";
Integer idExtraido = PipelineDados.processarEntrada(dadoBruto, s -> {
String idStr = s.split(";")[0].split(":")[1];
return Integer.parseInt(idStr);
});
System.out.println("ID Processado: " + idExtraido);
}
}
O uso de callbacks em Java transforma códigos rígidos em estruturas modulares e altamente adaptáveis, facilitando a manutenção e a legibilidade do software.