O Spring Framework é um ecossistema robusto e de código aberto para o desenvolvimento de aplicações Java, conhecido por sua arquitetura modular e leveza. Atuando essencialmente como um contêiner para gerenciar componentes, seus pilares fundamentais são a Inversão de Controle (IoC) e a Programação Orientada a Aspectos (AOP).
Benefícios Chave do Spring
O Spring oferece diversas vantagens que o tornam uma escolha popular no desenvolvimento corporativo:
- Contêiner de Gerenciamento: O Spring funciona como um contêiner que assume a responsabilidade de gerenciar o ciclo de vida e as dependências dos objetos (também conhecidos como beans) de uma aplicação. Isso simplifica a arquitetura e promove a organização.
- Inversão de Controle (IoC): Este princípio central delega ao framework a criação e o gerenciamento das instâncias de objetos, em vez de o desenvolvedor instanciá-los manualmente. Isso promove um design mais desacoplado e facilita a testabilidade.
- Programação Orientada a Aspectos (AOP): A AOP permite modularizar preocupações transversais, como logging, segurança ou gerenciamento de transações. Essas funcionalidades podem ser aplicadas a diversos pontos da aplicação sem alterar o código principal, geralmente por meio de proxies dinâmicos, o que resulta em um código mais limpo e modular.
Inversão de Controle (IoC) e Injeção de Dependência (DI)
A Inversão de Controle (IoC) é um princípio de design que transfere a responsabilidade de instanciar e gerenciar o ciclo de vida dos objetos de uma aplicação do desenvolvedor para um contêiner. Em vez de um componente criar suas próprias dependências, ele as recebe do contêiner. O objetivo principal é alcançar um alto grau de desacoplamento entre os componentes da aplicação.
A Injeção de Dependência (DI) é uma forma específica e mais comum de implementar a IoC. Nela, o contêiner Spring 'injeta' automaticamente as dependências necessárias em um objeto no momento de sua criação ou inicialização. Por exemplo, se uma classe de serviço depende de uma classe de repositório, o Spring automaticamente fornecerá uma instância do repositório ao serviço. A DI é fundamental para criar aplicações modulares e fáceis de manter.
Interfaces BeanFactory e ApplicationContext
No coração do Spring, as interfaces BeanFactory e ApplicationContext representam os contêineres IoC. A BeanFactory é a interface mais básica e original, operando de forma lazy-loading: os beans são instanciados somente quando solicitados explicitamente através do método getBean(). Isso a torna mais leve, mas com menos funcionalidades.
A ApplicationContext é uma extensão da BeanFactory, oferecendo recursos adicionais como internacionalização (i18n), publicação de eventos, e a capacidade de registrar listeners. Ao contrário da BeanFactory, a ApplicationContext geralmente inicializa todos os beans singleton no momento em que é carregada (eager-loading), o que pode consumir mais recursos no início, mas garante que os beans estejam prontos para uso imediato.
Um exemplo simplificado de como carregar um ApplicationContext:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public interface Mensageiro {
void enviarSaudacao();
}
public class SaudadorBasico implements Mensageiro {
@Override
public void enviarSaudacao() {
System.out.println("Olá, mundo Spring!");
}
}
public class AplicacaoCliente {
public static void main(String[] args) {
// Carrega o contexto da aplicação a partir de um arquivo XML
ApplicationContext contexto = new ClassPathXmlApplicationContext("configuracao-app.xml");
// Obtém o bean "saudador" do contêiner
Mensageiro meuSaudador = (Mensageiro) contexto.getBean("saudador");
// Invoca um método do bean
meuSaudador.enviarSaudacao();
}
}
E o arquivo XML de configuração (configuracao-app.xml):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="saudador" class="SaudadorBasico"/>
</beans>
Configuração de Beans com Tags XML
A tag <bean> é utilizada no Spring para descrever os objetos que serão gerenciados pelo contêiner. Através dela, é possível configurar detalhes como o nome do bean, sua classe e o escopo.
id(ouname): Um identificador único para o bean dentro do contêiner, usado para referenciá-lo.class: O nome totalmente qualificado da classe do objeto a ser instanciado.scope: Define o ciclo de vida do bean. Os valores mais comuns são:singleton(padrão): Uma única instância do bean é criada e compartilhada por todas as requisições. Adequado para beans sem estado (stateless).prototype: Uma nova instância do bean é criada a cada requisição. Essencial para beans com estado (stateful), como objetos Action em frameworks web mais antigos.request,session,globalSession: Escopos específicos para aplicações web, onde um bean é associado ao ciclo de vida de uma requisição HTTP, uma sessão HTTP, ou uma sessão HTTP global, respectivamente.
Exemplo de definição de bean:
<bean id="servicoDePedidos" class="com.example.app.service.ServicoDePedidosImpl" scope="singleton"/>
As formas de instanciar beans com a tag <bean> incluem:
- Construtor Padrão (sem argumentos): A forma mais comum, onde o Spring invoca o construtor público sem parâmetros da classe.
- Método de Fábrica Estático: O Spring invoca um método estático da classe para obter uma instância do bean.
- Método de Fábrica de Instância: O Spring invoca um método de uma instância de fábrica já existente para criar o bean.
Métodos de Injeção de Propriedades
O Spring oferece várias maneiras de injetar valores ou referências de objetos em propriedades de um bean:
- Injeção por Método Setter: O Spring invoca métodos setters nas propriedades do bean.
- Injeção por Construtor: As dependências são passadas como argumentos para o construtor do bean.
- Injeção com Namespace P (XML): Uma sintaxe XML mais concisa para injeção via setters.
- Injeção com SpEL (Spring Expression Language): Permite o uso de expressões para definir valores.
- Injeção de Tipos Complexos: Suporte para injeção de coleções como arrays,
List,MapeProperties.
Ciclo de Vida de um Bean
O ciclo de vida de um bean gerenciado pelo Spring inclui as seguintes etapas:
- Definição: O bean é declarado no arquivo de configuração (XML) ou via anotações.
- Inicialização: Após a instanciação, o Spring pode invocar métodos de inicialização (definidos com
init-methodem XML ou@PostConstructem anotações) para configurar o bean. - Utilização: O bean é utilizado pela aplicação após ser obtido do contêiner.
- Destruição: Antes de o contêiner ser encerrado, métodos de destruição (definidos com
destroy-methodem XML ou@PreDestroyem anotações) podem ser invocados para liberar recursos.
Configuração de Beans Baseada em Anotações
Para simplificar a configuração e reduzir a verbosidade do XML, o Spring oferece um poderoso suporte a anotações. Antes de usá-las, é necessário habilitar o escaneamento de componentes no arquivo de configuração Spring (geralmente via <context:component-scan base-package="seu.pacote.base"/> ou @ComponentScan).
Anotações de Registro de Componentes
Para registrar um objeto como um bean no contêiner Spring, as seguintes anotações são comuns:
@Component: Uma anotação genérica para qualquer componente gerenciado pelo Spring.@Service: Usada para classes na camada de serviço, indicando lógica de negócios.@Controller: Marcador para classes na camada de apresentação (geralmente para controladores MVC).@Repository: Indicado para classes na camada de persistência (DAO), com suporte a tradução de exceções de persistência.
Embora funcionalmente similares (todas são especializações de @Component), essas anotações fornecem clareza semântica, ajudando a organizar e entender a arquitetura da aplicação.
Definindo o Escopo com Anotações
Para especificar o escopo de um bean, utiliza-se a anotação @Scope:
@Component
@Scope("prototype") // ou "singleton", "request", etc.
public class MeuComponenteComEscopo {
// ...
}
Injeção de Dependência com Anotações
@Value: Injeta valores primitivos, strings ou expressões SpEL diretamente em campos ou métodos.@Autowired: A anotação mais comum para injeção de dependências por tipo. Pode ser aplicada em construtores, métodos setters ou campos. O Spring tenta encontrar um bean compatível pelo tipo; se houver mais de um, tenta pelo nome.@Qualifier: Usada em conjunto com@Autowiredpara especificar o nome do bean a ser injetado quando há múltiplas implementações do mesmo tipo.@Resource: Fornece injeção por nome, mas também pode injetar por tipo se o nome não for encontrado. É uma anotação padrão do JSR-250.
Programação Orientada a Aspectos (AOP) no Spring
A AOP visa modularizar preocupações transversais, que são funcionalidades que afetam várias partes da aplicação (ex: segurança, transações, logging, cache). Em vez de espalhar código repetitivo por toda a aplicação, a AOP permite concentrá-lo em "aspectos" e aplicá-los de forma declarativa.
A essência do Spring AOP reside na capacidade de gerar objetos proxy dinâmicos. Quando um método de um objeto gerenciado pelo Spring é invocado, o proxy intercepta a chamada e executa o código do aspecto antes, depois ou em torno da execução do método original.
Mecanismos de Proxy no Spring AOP
O Spring utiliza dois mecanismos principais para criar proxies dinâmicos:
- Proxies Dinâmicos JDK: Usados quando o objeto alvo implementa uma ou mais interfaces. O proxy gerado implementa as mesmas interfaces e delega as chamadas para o objeto alvo.
- Proxies CGLIB: Usados quando o objeto alvo não implementa nenhuma interface ou quando a configuração do Spring força o uso de CGLIB. CGLIB gera uma subclasse do objeto alvo em tempo de execução. Uma limitação é que classes e métodos marcados como
finalnão podem ser "proxyados" por CGLIB, pois a herança não é possível.
O Spring adota uma abordagem híbrida: se o objeto alvo implementar interfaces, o proxy JDK é preferido; caso contrário, o CGLIB é utilizado.
Terminologia AOP
- Joinpoint (Ponto de Junção): Qualquer ponto na execução de um programa onde um aspecto pode ser apliacdo (ex: chamada de método, lançamento de exceção, acesso a campo). No Spring AOP, um joinpoint é sempre a execução de um método.
- Pointcut (Ponto de Corte): Uma expressão que define um conjunto de joinpoints onde o advice deve ser aplicado.
- Advice (Conselho/Comportamento): A ação ou código a ser executado em um joinpoint específico.
- Target (Alvo): O objeto cujo método está sendo interceptado pelo aspect.
- Aspect (Aspecto): A modularização de uma preocupação transversal, combinando pointcuts e advices.
- Weaving (Tecelagem): O processo de aplicar os advices a um target em um pointcut específico para criar um objeto proxy.
- Proxy: O objeto criado pelo framework que encapsula o target e contém a lógica do advice.
Tipos de Advice no Spring AOP
O Spring AOP oferece cinco tipos de advice:
- Before (Antes): Executado antes do método do target.
- After (Finalmente): Executado após o método do target, independentemente de ter ocorrido uma exceção ou não.
- After Returning (Após Retorno): Executado somente após o método do target retornar com sucesso (sem lançar exceções).
- After Throwing (Após Exceção): Executado somente se o método do target lançar uma exceção.
- Around (Ao Redor): Envolve o método do target, permitindo que o advice execute código antes e depois do método, e até mesmo suprima a execução do método original.
Implementação de AOP no Spring
A implementação de AOP no Spring geralmente envolve:
- Adicionar as dependências AOP.
- Definir os objetos alvo (os beans a serem interceptados).
- Escrever a lógica do advice (o código a ser executado).
- Configurar o aspecto, definindo os pointcuts e associando-os aos advices. Isso pode ser feito via XML (usando tags
<aop:config>,<aop:pointcut>,<aop:aspect>e os advices específicos como<aop:before>,<aop:after-returning>, etc.) ou via anotações (@Aspect,@Pointcut,@Before,@AfterReturning, etc.).
Gerenciamento de Transações no Spring
O gerenciamento de transações é crucial para garantir a integridade dos dados em operações que envolvem o banco de dados. Uma transação é um conjunto de operações que são tratadas como uma única unidade lógica de trabalho: ou todas são concluídas com sucesso (commit), ou nenhuma delas é aplicada (rollback) se ocorrer uma falha.
Propriedades ACID das Transações
As transações seguem as propriedades ACID:
- Atomicidade (Atomicity): Uma transação é uma unidade indivisível. Todas as operações dentro dela devem ser bem-sucedidas para que a transação seja efetivada; caso contrário, todas as mudanças são revertidas.
- Consistência (Consistency): Uma transação deve levar o banco de dados de um estado consistente para outro estado consistente. Ela garante que todas as regras e restrições do banco de dados sejam respeitadas.
- Isolamento (Isolation): Transações concorrentes devem ser executadas de forma isolada, como se estivessem sendo executadas sequencialmente. As operações de uma transação não devem ser visíveis para outras transações até que ela seja efetivada.
- Durabilidade (Durability): Uma vez que uma transação é efetivada, suas mudanças são permanentes e devem sobreviver a falhas do sistema (como queda de energia).
Configuração de Transações Declarativas no Spring (XML)
A configuração de transações no Spring envolve três componentes principais:
- Gerenciador de Transações: O Spring fornece a interface
PlatformTransactionManager, com implementações específicas para diferentes tecnologias de persistência. Por exemplo,DataSourceTransactionManagerpara JDBC/MyBatis eHibernateTransactionManagerpara Hibernate. - Advice Transacional: Define as regras de transação (propagação, isolamento, somente leitura, timeout, regras de rollback).
- Aspecto Transacional: Vincula o advice transacional a métodos específicos da aplicação usando expressões de pointcut.
Exemplo de configuração XML para transações:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- Gerenciador de Transações (exemplo para JDBC) -->
<bean id="gerenciadorTransacoes"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="fonteDeDados"/>
</bean>
<!-- Advice Transacional -->
<tx:advice id="advTx" transaction-manager="gerenciadorTransacoes">
<tx:attributes>
<!-- Regras de propagação e leitura para diferentes métodos -->
<tx:method name="salvar*" propagation="REQUIRED"/>
<tx:method name="adicionar*" propagation="REQUIRED"/>
<tx:method name="atualizar*" propagation="REQUIRED"/>
<tx:method name="excluir*" propagation="REQUIRED"/>
<tx:method name="buscar*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="obter*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/> <!-- Padrão para outros métodos -->
</tx:attributes>
</tx:advice>
<!-- Definindo o Aspecto Transacional -->
<aop:config>
<aop:advisor advice-ref="advTx"
pointcut="execution(* com.minhaempresa.servico.*.*(..))"/>
</aop:config>
</beans>
Para Hibernate, o gerenciador de transações seria:
<bean id="gerenciadorTransacoesHibernate"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="fabricaDeSessaoHibernate"/>
</bean>
Comportamentos de Propagação de Transação
Definem como as transações se comportam quando um método transacional é chamado a partir de outro método transacional. Existem sete tipos:
REQUIRED(padrão): Suporta a transação atual; se nenhuma existir, uma nova é criada.REQUIRES_NEW: Sempre inicia uma nova transação e suspende a transação atual, se houver.SUPPORTS: Suporta a transação atual; se nenhuma existir, executa sem transação.NOT_SUPPORTED: Executa sem transação e suspende a transação atual, se houver.MANDATORY: Requer uma transação existente; caso contrário, lança uma exceção.NESTED: Executa em uma transação aninhada dentro da transação existente (semelhante a savepoints).NEVER: Requer que não haja nenhuma transação; caso contrário, lança uma exceção.
Problemas de Concorrência e Níveis de Isolamento
Em ambientes multiusuários, transações concorrentes podem levar a problemas como:
- Leitura Suja (Dirty Read): Uma transação lê dados que foram modificados por outra transação, mas ainda não foram efetivados. Se a segunda transação for revertida, os dados lidos pela primeira nunca existiram de fato.
- Leitura Não Repetível (Non-repeatable Read): Uma transação lê o mesmo dado duas vezes e obtém valores diferentes porque outra transação modificou e efetivou o dado entre as duas leituras.
- Leitura Fantasma (Phantom Read): Uma transação executa uma consulta que retorna um conjunto de linhas. Se, posteriormente, a mesma transação executar a mesma consulta e outras transações tiverem inserido novas linhas que se encaixam nos critérios da consulta, a primeira transação verá essas novas linhas ("fantasmas").
Para mitigar esses problemas, os bancos de dados fornecem Níveis de Isolamento de Transação, com um trade-off entre consistência e desempenho:
- READ UNCOMMITTED (Leitura Não Efetivada): Permite leituras sujas. É o nível menos restritivo.
- READ COMMITTED (Leitura Efetivada): Previne leituras sujas, mas permite leituras não repetíveis e fantasmas. Muitos bancos de dados usam este como padrão.
- REPEATABLE READ (Leitura Repetível): Previne leituras sujas e não repetíveis, mas pode permitir leituras fantasmas.
- SERIALIZABLE (Serializável): O nível mais restritivo. Garante a consistência total, prevenindo todos os problemas (leituras sujas, não repetíveis e fantasmas), mas pode impactar severamente o desempenho ao serializar o acesso aos dados.
Timeout de Transação e Regras de Rollback
O timeout de transação define o tempo máximo que uma transação pode levar para ser concluída. Se excedido, a transação é automaticamente revertida. As regras de rollback especificam quais exceções devem ou não causar o rollback da transação. Por padrão, o Spring reverte a transação para exceções de tempo de execução (RuntimeException) e erros, mas não para exceções checadas (CheckedException).
Integração do Spring com Outros Frameworks (SSM - Spring, Spring MVC, MyBatis)
Ao integrar o Spring com outros frameworks como Spring MVC e MyBatis, diversas configurações são necessárias para que funcionem em conjunto:
- Integração com a Camada DAO (MyBatis):
- Configuração da fonte de dados (DataSource).
- Definição do
SqlSessionFactorydo MyBatis. - Configuração do escaneamento de mappers (interfaces DAO) para criação de proxies dinâmicos do MyBatis.
- Integração com a Camada de Serviço:
- Habilitação do escaneamento de componentes (
@ComponentScan) para serviços. - Configuração do gerenciamento de transações (gerenciador de transações, advice transacional e aspecto transacional) para garantir a atomicidade das operações de negócio.
- Habilitação do escaneamento de componentes (
- Integração com a Camada Web (Spring MVC):
- Habilitação do escaneamento de componentes para controladores (
@Controller). - Configuração de um
HandlerMappingeHandlerAdapterpara processar requisições web (geralmente via<mvc:annotation-driven/>). - Definição de um resolvedor de视图 (
ViewResolver) para mapear nomes lógicos de视图 para arquivos de视图 físicos.
- Habilitação do escaneamento de componentes para controladores (
- Configurações Gerais no
web.xml:- Carregamento do contêiner Spring principal (
ContextLoaderListener). - Configuração do
DispatcherServletdo Spring MVC como controlador frontal para interceptar e despachar requisições web.
- Carregamento do contêiner Spring principal (
Principais Conceitos Utilizados no Spring
Em suma, os elementos mais frequentemente empregados no Spring incluem:
- Inversão de Controle (IoC)
- Injeção de Dependência (DI)
- Injeção de Propriedades (via setters, construtores, anotações)
- Gerenciamento de Transações (declarativo e programático)
- Programação Orientada a Aspectos (AOP)
- Beans e seus escopos (singleton, prototype, web-aware)
- Configuração baseada em XML e anotações