ApplicationContext — o container e seu ciclo

TL;DR

ApplicationContext é a face completa do container Spring: ele estende BeanFactory com eventos, internacionalização, carregamento de recursos e abstração de ambiente. Em produção, você quase nunca usa BeanFactory diretamente — o Spring Boot entrega um ApplicationContext já configurado. O ciclo de vida do container gira em torno do método refresh(): ao ser chamado, ele carrega definições, instancia singletons e dispara ContextRefreshedEvent. Entender esse ciclo é essencial para debug de inicialização, integração com frameworks web e escrita de extensões do container.

O que é

ApplicationContext é a interface central do módulo spring-context. Ela herda de BeanFactory (que resolve e fornece beans) e acrescenta um conjunto de contratos orientados a aplicação:

  • MessageSource — internacionalização (i18n) com resolução por locale
  • ResourceLoader — acesso transparente a arquivos, URLs e resources do classpath
  • ApplicationEventPublisher — publicação de eventos para listeners registrados
  • HierarchicalBeanFactory — suporte a contextos pai/filho (ex.: root + web layer)
  • EnvironmentCapable — acesso a perfis ativos e propriedades de configuração

A distinção prática é que BeanFactory faz o mínimo (resolve beans sob demanda) enquanto ApplicationContext é o framework completo, incluindo pós-processadores, eventos de ciclo de vida e suporte a AOP.

Por que importa

Em entrevistas e no dia a dia, o candidato precisa saber:

  1. Qual interface usar — você injeta ApplicationContext quando precisa publicar eventos ou carregar recursos dinamicamente; para resolver beans diretamente (getBean()), a prática é um anti-pattern (veja Armadilhas).
  2. O que acontece antes do main retornar — o Spring Boot chama refresh() automaticamente; falhas de wiring aparecem aqui, não em runtime tardio.
  3. Como estender o containerBeanFactoryPostProcessor, BeanPostProcessor e interfaces *Aware são os pontos de extensão canonicamente usados por bibliotecas (Spring Security, Spring Data, etc.).

Como funciona

BeanFactory vs ApplicationContext (o que o segundo adiciona)

BeanFactory resolve beans lazily e não carrega extensões automaticamente. ApplicationContext, ao chamar refresh(), executa uma sequência enriquecida:

CapacidadeInterface adicionadaDetalhe
InternacionalizaçãoMessageSourceProcura bean messageSource; fallback: DelegatingMessageSource vazio
Acesso a recursosResourceLoaderclasspath:, file:, http: transparentes
Publicação de eventosApplicationEventPublisherSincrono por padrão; pode ser tornado assíncrono via SimpleApplicationEventMulticaster com TaskExecutor
Hierarquia de contextosHierarchicalBeanFactoryRoot context (serviços/repositórios) + child context (web/MVC)
Abstração de ambienteEnvironmentCapablePerfis (@Profile) e properties (@Value, Environment)

Em aplicações simples, BeanFactory nunca é instanciada diretamente. Ela aparece principalmente em ambientes com severa restrição de memória (IoT, Android) ou como abstração interna de integrações legadas.

Tipos concretos (standalone, servlet, reactive)

O Spring oferece implementações para cada estilo de deploy:

Standalone (linha de comando ou testes):

// Configuração por anotações — o mais comum hoje
AnnotationConfigApplicationContext ctx =
    new AnnotationConfigApplicationContext(AppConfig.class);
 
// Configuração XML — legado, ainda válido
ClassPathXmlApplicationContext ctx =
    new ClassPathXmlApplicationContext("beans.xml");

Servlet (Spring MVC / Spring Boot + Tomcat/Jetty):

  • AnnotationConfigWebApplicationContext — usado pelo DispatcherServlet
  • XmlWebApplicationContext — suporta “hot refresh” (recarregar sem reiniciar JVM)
  • GenericWebApplicationContext — não suporta hot refresh; padrão no Spring Boot moderno

Reactive (Spring WebFlux / Spring Boot + Netty):

  • AnnotationConfigReactiveWebServerApplicationContext — entregue automaticamente pelo Boot quando a pilha WebFlux é detectada no classpath

O Spring Boot escolhe a implementação correta baseado no classpath: ausência de spring-webmvc e spring-webflux → standalone; presença de spring-webmvc → servlet; presença de spring-webflux (sem MVC) → reactive.

refresh() e o ciclo do container

refresh() é o coração do AbstractApplicationContext. Ao ser invocado, ele executa uma sequência determinista de fases:

  1. Preparação do environment — valida propriedades obrigatórias
  2. Carregamento de BeanDefinitions — lê anotações, XML ou código Java; registra metadados (não instâncias)
  3. Execução de BeanFactoryPostProcessors — podem modificar definições antes da instanciação (ex.: PropertySourcesPlaceholderConfigurer resolve ${...})
  4. Registro de BeanPostProcessors — hooks para envolver beans após criação (AOP proxies, @Autowired)
  5. Inicialização de infraestruturaMessageSource, ApplicationEventMulticaster
  6. Instanciação de singletons non-lazy — todos os beans eager são criados aqui
  7. Publicação de ContextRefreshedEvent — sinal de que o container está pronto

Eventos do ciclo de vida do contexto:

EventoDisparado porSignificado
ContextRefreshedEventrefresh()Container pronto; todos os singletons instanciados
ContextStartedEventstart()Beans Lifecycle recebem sinal de start explícito
ContextStoppedEventstop()Beans Lifecycle recebem sinal de stop explícito
ContextClosedEventclose()Singletons destruídos; contexto encerrado

Hot refresh

XmlWebApplicationContext suporta chamar refresh() múltiplas vezes (hot refresh). GenericApplicationContext — e portanto AnnotationConfigApplicationContextnão suporta: uma segunda chamada a refresh() lança exceção. O Spring Boot usa a variante que não suporta hot refresh por padrão.

Interfaces *Aware

O container detecta beans que implementam interfaces *Aware durante a fase de pós-processamento e injeta a dependência correspondente via setter, antes de qualquer @PostConstruct:

InterfaceMétodo injetadoO que é fornecido
ApplicationContextAwaresetApplicationContext()O próprio ApplicationContext
BeanNameAwaresetBeanName()O ID do bean no container
BeanFactoryAwaresetBeanFactory()A BeanFactory subjacente
ApplicationEventPublisherAwaresetApplicationEventPublisher()Publisher para emitir eventos
MessageSourceAwaresetMessageSource()MessageSource para i18n
ResourceLoaderAwaresetResourceLoader()ResourceLoader para carregar arquivos
EnvironmentAwaresetEnvironment()Environment com perfis e properties
EmbeddedValueResolverAwaresetEmbeddedValueResolver(StringValueResolver)Resolvedor de placeholders ${...} para uso em código de infraestrutura

Interfaces *Aware são usadas principalmente por código de infraestrutura e integrações. Em código de aplicação, prefira injeção via @Autowired ou construtor.

Na prática

Em 99% dos casos, você não injeta ApplicationContext diretamente — o Spring resolve dependências via @Autowired ou construtor sem que o bean precise saber que há um container.

Os casos legítimos de injeção são:

  • Publicação de eventos — prefira ApplicationEventPublisher (interface mais estreita)
  • Carregamento dinâmico de recursosResourceLoader ou ResourcePatternResolver
  • Lookup de bean por tipo em runtime — raro, mas existem cenários (fábricas dinâmicas)
// Caso legítimo: publicar evento de domínio
@Service
public class OrderService implements ApplicationEventPublisherAware {
 
    private ApplicationEventPublisher publisher;
 
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
 
    public void placeOrder(Order order) {
        // lógica de negócio...
        publisher.publishEvent(new OrderPlacedEvent(this, order.getId()));
    }
}
 
// Alternativa idiomática no Spring 4.2+: injeção via @Autowired
@Service
public class OrderService {
 
    private final ApplicationEventPublisher publisher;
 
    public OrderService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
 
    public void placeOrder(Order order) {
        publisher.publishEvent(new OrderPlacedEvent(this, order.getId()));
    }
}

Prefira interfaces estreitas

Se precisar apenas publicar eventos, injete ApplicationEventPublisher. Se precisar apenas de resources, injete ResourceLoader. Injetar o ApplicationContext completo expõe mais superfície do que necessário e é sinal de que o design pode ser melhorado.

Armadilhas

(1) Service-locator anti-pattern com getBean()

Chamar applicationContext.getBean(ProductRepository.class) dentro de um @Service transforma o container num localizador de serviços: o código passa a depender do container explicitamente, o que dificulta testes (você precisa de um contexto real ou mockar o ApplicationContext) e esconde dependências da assinatura do construtor.

// ERRADO — service locator oculta dependências
@Service
public class ProductService {
 
    @Autowired
    private ApplicationContext ctx;
 
    public Product findProduct(Long id) {
        ProductRepository repo = ctx.getBean(ProductRepository.class); // anti-pattern
        return repo.findById(id).orElseThrow();
    }
}
 
// CORRETO — dependência declarada no construtor
@Service
public class ProductService {
 
    private final ProductRepository repo;
 
    public ProductService(ProductRepository repo) {
        this.repo = repo;
    }
 
    public Product findProduct(Long id) {
        return repo.findById(id).orElseThrow();
    }
}

Fix: declare todas as dependências no construtor ou via @Autowired em campo/setter.

(2) Assumir lazy onde o padrão é eager

Singletons são instanciados durante refresh(), não na primeira chamada. Desenvolvedores que esperam inicialização tardia podem se surpreender com exceções de wiring no startup — antes de qualquer request chegar.

// O bean abaixo falha no startup se DataSource não estiver configurado,
// NÃO na primeira chamada a findAll()
@Repository
public class CustomerRepository {
 
    private final JdbcTemplate jdbc;
 
    public CustomerRepository(DataSource ds) {
        this.jdbc = new JdbcTemplate(ds); // lançado durante refresh()
    }
}

Fix: se inicialização tardia for necessária (ex.: integração opcional), use @Lazy ou @ConditionalOn* do Boot para não registrar o bean quando o recurso estiver ausente. Em testes, prefira @SpringBootTest com contexto completo para detectar falhas de wiring cedo.

(3) Segurar referência ao contexto além do escopo necessário

Guardar o ApplicationContext em variável estática (padrão comum em código legado para integração com frameworks sem DI) impede que o GC libere o contexto após o shutdown e causa vazamentos em ambientes de hot-deploy (servidores de aplicação que fazem reload de webapps).

// PROBLEMÁTICO — vazamento em hot-deploy
public class LegacyBridge {
    private static ApplicationContext ctx; // mantém classloader vivo
 
    public static void init(ApplicationContext context) {
        ctx = context;
    }
}

Fix: injete as dependências específicas de que LegacyBridge precisa (ex.: o serviço ou repositório) em vez do contexto inteiro. Se a integração for inevitável, registre um ApplicationListener<ContextClosedEvent> para limpar a referência estática no shutdown.

Em entrevista

Frase pronta (inglês)

ApplicationContext extends BeanFactory with enterprise features: event publishing, internationalization, resource loading, and environment abstraction. The container lifecycle revolves around the refresh() method, which loads bean definitions, runs post-processors, and pre-instantiates all non-lazy singletons — raising ContextRefreshedEvent when ready. In practice, you should avoid injecting ApplicationContext directly and rely on constructor injection instead; using getBean() as a service locator is a well-known anti-pattern that hides dependencies and makes testing harder.”

Vocabulário

Termo PTTermo EN
Container de inversão de controleIoC container
Contexto de aplicaçãoApplication context
Definição de beanBean definition
Pós-processador de beanBean post-processor
Carregamento antecipado (padrão)Eager instantiation (default)
Carregamento tardioLazy initialization
Publicação de eventoEvent publishing
Escuta de eventoEvent listening
Abstração de ambienteEnvironment abstraction
Localizador de serviços (anti-pattern)Service locator (anti-pattern)

Veja também

Referências