Segundo seu criador, o objetivo do Spring é facilitar a criação de aplicações J2EE, reduzindo o esforço e o custo de desenvolvimento, e ao mesmo tempo melhorando a cobertura de testes e a qualidade.
Na prática, ele preenche uma lacuna, a da camada de negócio.
No início, era o MVC. Pioneiro, o Struts criou uma base para a implementação da apresentação da aplicação, incluindo controle de navegação, validação e reúso de fragmentos (Tiles). Mais tarde, vários outros frameworks para a camada de apresentação foram criados, alguns que seguiram a linha do Struts, implementando o padrão MVC, outros tentando novos modelos, como o Tapestry e o JSF, que utilizam uma abstração de componentes e eventos.
Depois, foi a vez da camada de persistência. Mapeadores O/R como o Hibernate, o OJB e o iBatis surgiram para facilitar o uso do banco de dados, oferecendo um modelo mais orientado a objetos, mesmo sobre um banco de dados relacional, além de promover a portabilidade entre bancos de dados.
Entre as camadas de apresentação e de persistência, fica a camada de negócio, ou de aplicação. É lá que a dita lógica de negócio se concentra, fazendo a ligação entre o armazenamento e a visualização/manipulação dos dados. E é nessa camada que a lógica específica da aplicação, isto é, que é mais ligada intimamente ao domínio do negócio, e não à tecnologia da implementação, se localiza.
Os outros frameworks (apresentação e persistência) resolvem problemas tecnológicos, respectivamente as limitações e complexidades das APIs Servlet e JDBC. Mas como é possível simplificar a implementação de uma camada que é constituída basicamente de lógica específica, que não pode ser generalizada e automatizada facilmente por um framework?
Bem, não é bem assim. A camada de negócio, tradicionalmente, fica longe de conter apenas lógica de negócio. Isso porque ela ainda tem que lidar com diferentes APIs, incluindo as dos frameworks de apresentação e de persistência, além de ser necessário algum tipo de gerenciamento e organização das classes. Assim, o código de negócio normalmente fica enterrado em um emaranhado de blocos try/catch/finally, para lidar com SQLExceptions, HibernateExceptions, etc. Além disso, muitas vezes os desenvolvedores se vêem na necessidade de abusar de alguns padrões de projeto, como o Abstract Factory e o Singleton. Deste modo, mais da metade do código da camada de negócio acabava lidando com aspectos tecnológicos, e não com regras negócio.
Os Enterprise JavaBeans (EJB) foram originalmente criados para fazer parte desta camada, e especificavam um modelo de componentes, com formas de acesso a recursos, controle transacional, remotabilidade e gerenciamento de ciclo de vida. Porém, a complexidade necessária para fornecer estas funcionalidades era alta, além de forçar um forte acoplamento ao container EJB com suas APIs invasivas.
O Spring surgiu para simplificar este cenário. Ele foi projetado para ser modular e o menos invasivo possível, e ao mesmo tempo flexível e poderoso. O framework foi construído sobre o conceito da Inversão de Controle (Inversion of Control, ou IoC), fazendo uso extenso de um container de injeção de dependências (Dependency Injection, ou DI), um framework simplificado de AOP, e de classes auxiliares que ajudam na integração com outros frameworks, encapsulando detalhes da tecnologia e fornecendo hooks de customização via callbacks.
Bom, o parágrafo anterior contém vários jargões, que tento explicar a seguir:
- Inversão de Controle: Também chamado de "Princípio de Hollywood", isto é, "Don't call us, we call you". Esta é uma característica comum aos frameworks, que implementam o fluxo principal de parte do sistema, fornecendo pontos de extensão para o desenvolvedor de aplicação adicionar a lógica específica. Assim, não é a aplicação que 'chama' o framework (como funcionam as bibliotecas), mas sim o framework que 'chama' os módulos da aplicação.
- Injeção de Dependências: Definido por Martin Fowler, este padrão descreve um modo de gerência de componentes e resolução de dependências onde as dependências dos componentes (recursos como DataSources, configurações e outros componentes) são 'injetadas' pelo container na inicialização do componente, se contrapondo ao tradicional Service Locator, onde o componente pede um recurso ao locator. Esta inversão faz com que toda a configuração seja feita no container, retirando a necessidade da criação de fábricas customizadas. Isto facilita a execução de testes (pode-se usar objetos de teste diretamente, ao invés de ter que se criar uma fábrica de objetos de teste) e promove o desacoplamento entre os componentes.
- Callbacks: Interfaces que encapsulam uma determinada parte de um algoritmo, chamadas pela implementação concreta do algoritmo. Um exemplo no Spring seria a execução de uma query SQL: Normalmente, você teria que obter uma conexão com o banco, criaria um PreparedStatement, informaria os parâmetros, executaria a consulta, transformaria os dados do ResultSet em algum objeto ou lista, trataria as exceções e liberaria os recursos. Os 'templates' do Spring implementam toda a lógica, exceto a leitura e transformação do ResultSet. Apenas este trecho do algoritmo é implementado pelo desenvolvedor da aplicação, na forma de um callback, sem se preocupar com a execução da consulta e a liberação de recursos.
- AOP: A programação orientada a aspectos é um outro pardigma de programação, complementar à orientação a objetos, que visa modularizar aspectos que permeiam os limites entre objetos, como gerenciamento de recursos, logging, controle de transações, etc. O Spring fornece uma biblioteca de 'componentes AOP', que simplificam o desenvolvimento, controlando coisas como conexões com o banco e transações de maneira centralizada e totalmente transparente às classes de negócio.