Spring.NET – Parte 1 – Dependency Injection

Objetivo

O objetivo desta “série” de artigos sobre o Spring.Net é explicar em linhas gerais o que é o Spring e como a vida melhora usando ele nos seus projetos.

Por ser uma framework que implementa muitas coisas, vou abordá-la em partes. Basicamente a idéia é explicar o application context (container) que ajuda a implementar os patterns de injeção de dependência (Dependency Injection – DI ou Inversion of Control – IoC), depois explicar os ganhos com algumas coisas de AOP (Aspect Oriented Programming ou Programação Orientada a Aspectos) “prontas” no Spring e por último conceitos de AOP e como implementar aspectos no Spring.

Vamos por partes… é bastante coisa.

O que é o Spring.Net?

O Spring.Net é uma versão portada para .Net da popular biblioteca “Spring” para Java, existente já a alguns anos e utilizada largamente em Java.

O objetivo principal da biblioteca Spring é implementar uma “infra-estrutura” poderosa para a aplicação bem como estimular a aplicações que usam o Spring a adotarem patterns de arquitetura já consagrados (como Dependency Injection).

A biblioteca está dividida em vários módulos (tem outros, vou citar só os principais):

  • Spring.Core – IoC Container, implementa o pattern de injeção de dependência ou inversão de controle (IoC, Inversion of Control).
  • Spring.Aop – Framework para programação orientada a aspectos
  • Spring.Data – Implementa uma camada de acesso a dados com alguma funcionalidade sobre o tradicional ADO.Net e principalmente transações declarativas.
  • Spring.Web – Possibilidade de “injetar dependências” em páginas aspx, implementação de Web Services no modelo Spring e outros.

Por que usar Injeção de Dependência?

O pattern de injeção de dependência é uma forma de minimizar o acoplamento das camadas da aplicação. Hoje em dia fala-se muito de desenvolvimento em camadas, reuso, etc. Quer provar se a sua aplicação é realmente em camadas?

Responda para si mesmo a pergunta: Eu consigo trocar minha camada de acesso a dados escrita na mão em ADO.Net para uma escrita em NHibernate?

Se a resposta for não ou talvez, é bem provável que suas camadas ainda tenham um acoplamento muito alto. O pattern de injeção de dependência vem para ajudar a minimizar esse acoplamento.

A idéia central está sempre em programar contra interfaces, e as camadas só conhecerem classes de outras camadas através da interface. Ué… se é simples assim, pra que eu preciso do Spring então?

Se vc estiver instânciando essas classes manualmente na sua aplicação, vc continua fazendo os módulos terem interdependência entre si. Com o Spring é possível tornar todo esse processo de instanciar e “amarrar” as instâncias através de configuração, de forma que fica muito mais simples “trocar” as camadas, e forçar o baixo acoplamento na sua aplicação.

Na prática

Baixando o Spring

O Spring pode ser encontrado e baixado em http://www.springframework.net. A versão que usamos aqui é a 1.2.

Criando uma Web Application de Exemplo

Esse exemplo aqui não segue nenhum tipo de convenção, padrão de arquitetura, nomenclatura nem nada. Essa não é a preocupação desse artigo.

Uma série de assemblies serão necessários para ilustrar as “camadas” da aplicação. Faremos esse trabalho nesse primeiro artigo, mas esse trabalho será reutilizado nos posteriores.

Vá no seu Visual Studio e crie uma novo Web Site.

Model

Crie uma class library dentro da solution, vamos batizá-la de “Model” (modelo), para ilustrar o assembly que vai conter nossos VO’s (Value objects, objetos anêmicos, class custom, ou como quer que queiram chamar isso).

Dentro da class library “Model” vamos criar uma classe “ItemPedido”, com o seguinte conteúdo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Model {
  [Serializable]
  public class ItemPedido {
    private int _ItemPedidoID;
    public int ItemPedidoID{
      get { return _ItemPedidoID;}
      set { _ItemPedidoID = value;}
    }
    
    private int _PedidoID;
    public int PedidoID{
      get { return _PedidoID;}
      set { _PedidoID = value;}
    }
  
    private int _ProdutoID;
    public int ProdutoID{
      get { return _ProdutoID;}
      set { _ProdutoID = value;}      
    }
    
    private float _Quantidade;
    public float Quantidade{
      get { return _Quantidade;}
      set { _Quantidade = value;}
    }
    
    private float _PrecoUnitario;
    public float PrecoUnitario{
      get { return _PrecoUnitario;}
      set { _PrecoUnitario = value;}
    }            
  }
}

Dentro da nossa class library “Model”, vamos criar uma classe “Pedido”, com o seguinte conteúdo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Model {
  [Serializable]
  public class Pedido {
    private int _PedidoID;
    public int PedidoID{
      get { return _PedidoID;}
      set { _PedidoID = value;}
    }
    
    private int _ClienteID;
    public int ClienteID{
      get { return _ClienteID;}
      set { _ClienteID = value;}
    }
    
    private float _ValorTotal;
    public float ValorTotal{
      get{ return _ValorTotal;}
      set {_ValorTotal  = value;}
    }
    
    private List<ItemPedido> _Itens = new List<ItemPedido>();
    public List<ItemPedido> Itens {
      get { return _Itens;}
      set { _Itens = value;}
    }
     
  }
}

Essa classe representa uma “estrutura de dados” que vai trafegar entre as camadas. Deve ser a camada com menor número de dependências possíveis, pois será usada em todas as outras camadas da aplicação. Essa é a idéia de um “POCO” (Plain Old C# Object, ou “bom e velho objeto C#”), uma classe minimalista que consiga representar a estrutura de dados necessária para o modelo de negócio.

DAL – Camada de acesso a dados (Data Access Layer)

Vamos separar a implementação da nossa camada de acesso a dados em três assemblys: Um para interfaces, um para persistência em memória, outro para persistência em disco.

Interface

Criar uma nova class library na solution com o nome DAL.Interface. Dentro desse assembly criaremos uma interface chamada “IPedidoDAO” e outra chamada “IItemPedidoDAO”, com o seguinte código:

IPedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;

namespace DAL.Interface {
  public interface IPedidoDAO {
    void inserirPedido(Pedido p);
    void alterarPedido(Pedido p);
    void excluirPedido(Pedido p);
    Pedido pegarPorID(int PedidoID);
  }
}

IItemPedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;

namespace DAL.Interface {
  public interface IItemPedidoDAO {
    void InserirItemPedido(ItemPedido ip);
    void AlterarItemPedido(ItemPedido ip);
    void ExcluirItemPedido(ItemPedido ip);
    List<ItemPedido> LerPorPedidoID(int PedidoID);
  }
}

Como vemos, a interface descreve as operações de “CRUD” (Create, read, update e delete), para a tabela de forma “chapada”, ou seja, as assinaturas dos métodos necessárias para fazer a persistência e recuperação das informações.

Esse assembly precisa ter o “Model” referenciado, pois as estruturas “Pedido” e “ItemPedido” são usados aqui no nosso “contrato” com a camada de acesso a dados.

DAL.Memoria

Vamos criar um novo assembly DAL.Memoria. Esse assembly irá referenciar o assembly “Model” e o “DAL.Interface”, pois será uma implementação da nossa camada de acesso a dados com persistência em memória.

Esse assembly possui duas classes:

PedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using Model;

namespace DAL.Memoria {
  public class PedidoDAO: IPedidoDAO {
    #region IPedidoDAO Members
    
    private Dictionary<int, Pedido> _pedidos = new Dictionary<int,Pedido>();       

    public void inserirPedido(Model.Pedido p) {
      //Autoincremento
      int max = 0;
      foreach(KeyValuePair<int, Pedido> pair in _pedidos){
        if (pair.Key > max)
          max = pair.Key;
      }
      p.PedidoID = max + 1;      
      _pedidos.Add(p.PedidoID, p);
    }

    public void alterarPedido(Model.Pedido p) {
      if (!_pedidos.ContainsKey(p.PedidoID))
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());
            
      _pedidos[p.PedidoID] = p;
    }

    public void excluirPedido(Model.Pedido p) {
      if (!_pedidos.ContainsKey(p.PedidoID))
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());
        
      _pedidos.Remove(p.PedidoID);
    }

    public Model.Pedido pegarPorID(int PedidoID) {
      if (!_pedidos.ContainsKey(PedidoID))
        return null;
      
      return _pedidos[PedidoID];
    }

    #endregion
  }
}

ItemPedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using Model;

namespace DAL.Memoria {
  public class ItemPedidoDAO : IItemPedidoDAO {
    #region IItemPedidoDAO Members
    
    private Dictionary<int, ItemPedido> _itens = new Dictionary<int, ItemPedido>();

    public void InserirItemPedido(Model.ItemPedido ip) {
      //Autoincremento
      int max = 0;
      foreach (KeyValuePair<int, ItemPedido> pair in _itens) {
        if (pair.Key > max)
          max = pair.Key;
      }
      ip.ItemPedidoID = max + 1;
      _itens.Add(ip.ItemPedidoID, ip);
    }

    public void AlterarItemPedido(Model.ItemPedido ip) {
      if (!_itens.ContainsKey(ip.ItemPedidoID))
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      _itens[ip.ItemPedidoID] = ip;
    }

    public void ExcluirItemPedido(Model.ItemPedido ip) {
      if (!_itens.ContainsKey(ip.ItemPedidoID))
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      _itens.Remove(ip.ItemPedidoID);
    }

    public List<Model.ItemPedido> LerPorPedidoID(int PedidoID) {
      List<ItemPedido> result = 
        (from KeyValuePair<int, ItemPedido> pair in _itens
         where pair.Value.PedidoID == PedidoID
         select pair.Value).ToList<ItemPedido>();
        
      return result;
    }

    #endregion
  }
}

Como vemos, essa implementação é bem “burra”. Nâo controla concorrência, absolutamente nada. Apenas mantemos uma instância de um “Dictionary” que guardam a lista de pedidos e itens de pedido instanciados. Não é o objetivo deste artigo traçar um “modelo” para classe de persistência. Apenas ilustrar o nosso exemplo de isolamento em camadas.

DAL.Session

Vamos criar outra implementação para nossa camada de acesso a dados. Este novo assembly DAL.Session que vamos criar possui referência para Model e DAL.Interface também.

Essa implementação é muito similiar à primeira, porém, invés de guardar “em memória”, vamos guardar na “Session”. No final das contas é persistência em memória do mesmo jeito, mas a idéia aqui é apenas mostrar uma segunda implementação para a mesma idéia (acesso a dados) que atende ao mesmo “contrato” estabelecido com nossas interfaces IPedidoDAO e IItemPedidoDAO.

PedidoSessionDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using System.Web;
using System.Web.SessionState;
using Model;

namespace DAL.Session {
  public class PedidoSessionDAO : IPedidoDAO {

    #region IPedidoDAO Members       
    
    public void inserirPedido(Model.Pedido p) {
      //Autoincremento
      int max = 0;      
      foreach (string k in HttpContext.Current.Session) {
        if (!k.StartsWith("Pedido_"))
          continue;
        Pedido ped = (Pedido)HttpContext.Current.Session[k];
        if (ped.PedidoID > max)
          max = ped.PedidoID;
      }      
      p.PedidoID = max + 1;

      HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] = p;
    }

    public void alterarPedido(Model.Pedido p) {
      if (HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] == null)
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());

      HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] = p;
    }

    public void excluirPedido(Model.Pedido p) {
      if (HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] == null)
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());

      HttpContext.Current.Session.Remove("Pedido_" + p.PedidoID.ToString());
    }

    public Model.Pedido pegarPorID(int PedidoID) {
      return (Pedido)HttpContext.Current.Session["Pedido_" + PedidoID.ToString()];                    
    }

    #endregion
  }
}

ItemPedidoSessionDAO.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using System.Web.SessionState;
using System.Web;
using Model;

namespace DAL.Session {
  public class ItemPedidoSessionDAO : IItemPedidoDAO {
    #region IItemPedidoDAO Members    

    public void InserirItemPedido(Model.ItemPedido ip) {
      //Autoincremento
      int max = 0;
      foreach (string k in HttpContext.Current.Session.Keys) {
        if (!k.StartsWith("ItemPedido_"))
          continue;
        ItemPedido iped = (ItemPedido)HttpContext.Current.Session[k];
        if (iped.ItemPedidoID > max)
          max = iped.ItemPedidoID;
      }
      ip.ItemPedidoID = max + 1;

      HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] = ip;
    }

    public void AlterarItemPedido(Model.ItemPedido ip) {
      if (HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] == null)
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] = ip;
    }

    public void ExcluirItemPedido(Model.ItemPedido ip) {
      if (HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] == null)
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      HttpContext.Current.Session.Remove("ItemPedido_" + ip.ItemPedidoID.ToString());
    }

    public List<Model.ItemPedido> LerPorPedidoID(int PedidoID) {
      List<ItemPedido> result = new List<ItemPedido>();
      foreach (string k in HttpContext.Current.Session.Keys) {
        if (!k.StartsWith("ItemPedido_"))
          continue;

        ItemPedido ip = (ItemPedido)HttpContext.Current.Session[k];
        if (ip.PedidoID == PedidoID)
          result.Add(ip);
      }
      return result;
    }

    #endregion
  }
}

BLL

Agora vamos exemplificar uma camada “de negócio”. Uma camada que “consome” nossa camada de acesso a dados (que contém o comportamento simples de persistência) e adiciona alguma regra de negócio.

A mesma abordagem do DAL, será usada. Interface e Implementação.

BLL.Interface

Vamos criar o assembly BLL.Interface, mais uma class library que conterá a interface IPedidoBO. Esses serão os métodos que serão “consumidos” por nossa camada de apresentação.

Esse assembly precisa conhecer somente o “Model”. Não precisa conhecer nem a interface nem as implementações da nossa camada de negócio.

IPedidoBO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;

namespace BLL.Interface {
  public interface IPedidoBO {
    void inserirPedido(Pedido p);
    Pedido pegarPedidoPorID(int PedidoID);
  }
}

A idéia deste contrato é no método inserirPedido, consistir e gravar o pedido e seus itens e no método “pegarPedidoPorID” pegar um pedido da base com seus itens.

BLL.Implementation

Vamos criar outro assembly, para a “implementação” da nossa camada de negócio. Esse assembly por sua vez precisa referenciar o Model (pois precisa conhecer as estruturas de nossos VO’s), precisa conhecer BLL.Interface, a interface para nossa camada de negócio e DAL.Interface, nossa interface para a camada de negócio.

Note que na implementação da nossa camada de negócio não precisamos conhecer a implementação da nossa camada de acesso a dados. Apenas a interface

PedidoBO.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BLL.Interface;
using DAL.Interface;
using Model;

namespace BLL.Implementation {
  public class PedidoBO: IPedidoBO {
    #region IPedidoBO Members
    
    private IPedidoDAO _pedidoDAO;
    public IPedidoDAO pedidoDAO{
      set {_pedidoDAO = value;}
    }
    
    private IItemPedidoDAO _itemPedidoDAO;
    public IItemPedidoDAO itemPedidoDAO{
      set {_itemPedidoDAO = value;}
    }

    public void inserirPedido(Model.Pedido p) {
      //Calcula automaticamente o valor total do cabeçalho do pedido baseado nos itens. 
      //Só pra exemplificar uma regra de negócio.
      float valorTotal = 0;
      foreach(ItemPedido ip in p.Itens){
        valorTotal += (ip.Quantidade * ip.PrecoUnitario);
      }
      p.ValorTotal = valorTotal;
      
      //Persiste o pedido e seus itens.
      _pedidoDAO.inserirPedido(p);
      foreach(ItemPedido ip in p.Itens){
        ip.PedidoID = p.PedidoID; //Passa o ID com autoincremento para os filhos.
        _itemPedidoDAO.InserirItemPedido(ip);
      }
    }

    public Pedido pegarPedidoPorID(int PedidoID) {
      Pedido p = _pedidoDAO.pegarPorID(PedidoID);
      p.Itens = _itemPedidoDAO.LerPorPedidoID(p.PedidoID);
      return p;
    }

    #endregion
  }
}

Finalmente, a Web Application

Na nossa “Default.aspx”, vamos adicionar um label, “lblMensagem” para darmos algum feedback para nosso usuário, dois botões, btnSalvarPedido e btnRecuperarPedido e um text box txtPedidoID para que o usuário possa informar o ID do pedido. Fica mais ou menos assim:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <asp:Label runat="server" ID="lblMensagem" />
      <br />
      <asp:Button runat="server" id="btnSalvarPedido" OnClick="btnSalvarPedidoClick" Text="Salvar pedido" />
      <br />
      <asp:TextBox runat="server" ID="txtPedidoID" />
      <asp:Button runat="server" ID="btnRecuperarPedido" Text="Recuperar Pedido" OnClick="btnRecuperarPedidoClick" />
    </div>
    </form>
</body>
</html>

No code behind, Default.aspx.cs, vamos declarar uma propriedade para acessar nossa camada de negócio:

  private IPedidoBO _pedidoBO;
  public IPedidoBO pedidoBO{
    set {_pedidoBO = value;}
  }

Mais pra frente vamos explicar o que é isso.

Agora vamos escrever o código para salvar e recuperar o pedido, acessando nossos métodos de IPedidoBO:

protected void btnSalvarPedidoClick(object sender, EventArgs e) {
    Pedido p = new Pedido();
    p.ClienteID = 1;
    
    ItemPedido ip1 = new ItemPedido();
    ip1.PrecoUnitario = 5;
    ip1.Quantidade = 10;
    p.Itens.Add(ip1);
    
    ItemPedido ip2 = new ItemPedido();
    ip2.PrecoUnitario = 20;
    ip2.Quantidade = 12;
    p.Itens.Add(ip2);
    
    _pedidoBO.inserirPedido(p);
    lblMensagem.Text = "Pedido " + p.PedidoID.ToString() + " salvo com sucesso.";
  }
  
  protected void btnRecuperarPedidoClick(object sender, EventArgs e) {
    Pedido p = _pedidoBO.pegarPedidoPorID(Convert.ToInt32(txtPedidoID.Text));
    lblMensagem.Text = "Pedido " + 
      p.PedidoID.ToString() + " valor total " + 
      p.ValorTotal.ToString() + " possui " + p.Itens.Count.ToString() + " itens.";
  }

Nesse momento, se executarmos a aplicação e clicarmos no botão “Salvar Pedido” vamos perceber que não existe nada instanciado em _pedidoBO e tomamos um “NullReference” exception. É porque falta a configuração do “contexto” da aplicação. Agora entra o Spring.

Vamos alterar também o “pai” da nossa página. Ela não vai mais herdar de System.Web.UI.Page e sim de Spring.Web.UI.Page. Isso faz com que o Spring consiga “injetar” dependências na nossa página.

Configurando o contexto do Spring

A primeira coisa que temos que fazer é configurar o “application context”. O application context é um “pool” de objetos pré-instanciados no momento da subida da aplicação para que estes possam ser “injetados” em outros objetos/camadas (por isso o conceito de “injeção de dependência”).

Para configurar o Application Context, devemos adicionar a referência para o Spring.Core e o Spring.Web na nossa aplicação (elas estão no diretório de instalação do Spring, Arquivos de programas\Spring .Net 1.2)

Dentro do nó “configSections” da sua web.config, adicione os seguintes elementos:

<sectionGroup name="spring">
  <section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web"/>
  <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>

Isso está dizendo para sua aplicação que a Web.Config agora entende uma sessão “spring” de configuração. E dentro dessa sessão ela entende outras duas chamadas “context” e “objects”.

Dentro de , vamos adicionar o seguinte nó:

<add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>

Com isso, dizemos que todo request que terminar com “.aspx”, vai passar por esse “handler” do Spring (necessário para que seja possível aplicar injeção de dependência nas páginas).

Por último, dentro de , vamos adicionar o seguinte nó:

<add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/>

Isso tbém tem a ver com configuração de injeção de dependência do Spring em páginas aspx.

Como filho do nó vamos adicionar os seguintes nós:

  <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="PedidoDAO" type="DAL.Session.PedidoSessionDAO, DAL.Session" />
      <object id="ItemPedidoDAO" type="DAL.Session.ItemPedidoSessionDAO, DAL.Session" />
      <object id="PedidoBO" type="BLL.Implementation.PedidoBO, BLL.Implementation" autowire="byType" />
      <object type="~/Default.aspx" >
        <property name="pedidoBO" ref="PedidoBO" />
      </object>        
    </objects>
  </spring>

Aqui mora o “X” da questão. Vamos detalhar essa configuração.

  • O nó diz que a configuração dos objetos vai pra dentro do nó e . O Spring permite que os objetos a serem instanciados no contexto possam ser especificados em vários lugares (compilados como recursos em assembly, arquivos xml externos e outros).
  • Os nós abaixo de “” descrevem “instâncias de objetos nomeadas” que o Spring irá criar no momento da subida da aplicação Web. Aqui descrevemos o seguinte:
    • Na instância chamada PedidoDAO, Vamos criar um objeto do tipo PedidoSessionDAL, que vive no assembly DAL.Session (A sintaxe é sempre NomeDaClasse.Com.Namespace, NomeDoAssembly). O spring vai usar esse tipo para criar a instância via Reflection.
    • Na instância chamada ItemPedidoDAO, vai um objeto do tipo ItemPedidoSessionDAO.
    • Na instãncia chamada PedidoBO, vai um objeto do tipo PedidoBO. A cláusula “autowire byType”, significa que ao subir esse objeto no contexto, o Spring vai “automaticamente” varrer as propriedades públicas do nosso PedidoBO e, caso encontre algum objeto no contexto que implemente o tipo dessa propriedade, ele irá automaticamente “amarrar” a instância. Na prática, isso vai fazer com que as instâncias PedidoDAO e ItemPedidoDAO sejam automaticamente atribuídas no nosso PedidoBO.
    • Por último, dizemos que todos os objetos do tipo “~/Default.aspx”, terão sua propriedade pedidoBO, automaticamente atribuida para nossa instância chamada PedidoBO no contexto. Isso significa que sempre que cair um “request”, automaticamente o pedidoBO será “injetado” na nossa página aspx.

Aqui é que a brincadeira fica interessante. Se executarmos novamente nossa aplicação, vamos perceber que ao clicar no botão “Salvar Pedido”, a instância _pedidoBO automaticamente recebeu nossa instância “PedidoBO” e que as instâncias da camada de acesso a dados também foram atribuídas ao nosso PedidoBO, e com isso, magicamente nosso código funciona.

Sobre as referências, percebemos que, para “compilar” a aplicação, precisamos apenas do BLL.Interface e BLL.Model, pois são as únicas camadas que nosso aspx depende diretamente. Porém se subirmos a aplicação, vamos perceber que ela não executa, porque o Spring não consegue instanciar alguns tipos (a implementação do DAL e do BLL). Aí puxamos a referência para que ele consiga “subir” a aplicação.

Esse processo de “jogar a implementação” para dentro do diretório “final” da aplicação não necessariamente precisa ser feito “criando uma referência” (que força a cópia pro “bin” automaticamente). Se isso for feito via um processo de build automatizado por exemplo, nossa aplicação pode ser desenvolvida sem “conhecer” essas implementações (ter a referência), minimizando significativamente o acoplamento entre as camadas (nosso objetivo principal).

Conclusão

Somente para finalizarmos a idéia de “baixo acoplamento”, vamos fazer um último exercício. Vamos alterar os nossos nós que declaram os objetos PedidoDAO e ItemPedidoDAO para os seguintes:

      <object id="PedidoDAO" type="DAL.Memoria.PedidoDAO, DAL.Memoria" />
      <object id="ItemPedidoDAO" type="DAL.Memoria.ItemPedidoDAO, DAL.Memoria" />

Se executarmos a aplicação agora e fizermos algum debug, vamos perceber que via “configuração”, trocamos a implementação de nossa camada de acesso a dados. Se tivessemos feito implementação “SQLServer” e “Oracle”, invés de “Memoria” e “Session”, teríamos uma aplicação multi-banco. Interessante, né?

Por este exemplo conseguimos perceber que o Spring possui ferramentas poderosas para desenvolvermos toda a “configuração” das camadas da nossa aplicação com baixo esforço e sem “acoplarmos” assemblies desnecessários na nossa aplicação.

A idéia de programar contra interfaces sempre e “injetar as dependências” em tempo de execução, pode trazer ganho significativo no sentido de induzir nossos desenvolvedores a programarem código reusável.

Há quem diga que somente esse benefício não justifica o uso do Spring. Para essas pessoas peço que aguardem as próximas partes do tutorial sobre Spring. Quando falarmos de transações declarativas e AOP duvido que estas pessoas não conseguirão enxergar todos os benefícios da adoção desta belíssima framework.

O código fonte usado no exemplo está disponível em: http://ericlemes.wikidot.com/local–files/dotnet-spring-pt1/SpringParte1.zip.

Anúncios

14 comentários em “Spring.NET – Parte 1 – Dependency Injection

  1. Excelente artigo!

    Estou iniciando com Spring e Nhibernate e no Deploy …

    Na minha máquina funciona normalmente, quando faço o deploy na locaweb com todas as referencias usadas local, apresenta esta mensagem:

    Server Error in ‘/sac/Pages/Cliente’ Application.
    ——————————————————————————–

    Configuration Error
    Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

    Parser Error Message: Could not load file or assembly ‘Spring.Web’ or one of its dependencies. The system cannot find the file specified. (e:\home\csweb\Web\sac\web.config line 99)

    Source Error:

    Line 97:
    Line 98:
    Line 99:
    Line 100:
    Line 101:

    Source File: e:\home\csweb\Web\sac\web.config Line: 99

    ——————————————————————————–
    Version Information: Microsoft .NET Framework Version:2.0.50727.3603; ASP.NET Version:2.0.50727.4028

    Vc pode me ajudar?

    abinálio

    1. Abinalio,

      Aparentemente o seu problema trata-se de uma questão de deploy. Os assemblys do Spring.Net, como quaisquer outros, ou precisam estar no /bin da web app ou registrados na GAC.

      Provavelmente qdo vc instalou na sua máquina, vc deve ter feito a referência dele via GAC e não fez o deploy deste componente no Web Server. Eu particularmente prefiro mandar os assemblys sempre no /bin.

      Fico feliz que o artigo tenha sido útil para você.

      Abraço,

      Eric

      1. Como faço para desvincular do GAC(caso tenha feito desta forma) e fazer apenas pelo /BIN.

        A dll Spring.Web que ele reclama, está no Web Server.

        Estou acostumado com desnvolvimento DeskTop… na web vejo muitas diferenças …

        Abinálio

      2. Abinálio,

        Quando você faz a referência, existem 5 abas: .Net, COM, Projects, Browse. Quando você faz pela primeira, está referenciando da GAC. Quando faz pela segunda, está criando um Interop COM, quando faz da terceira, para uma class library dentro da própria solution. Quando faz da aba “Browse”, vc faz com que os arquivos sejam copiados para o /bin. Faça por aí.

        Eu geralmente costumo criar junto com um fonte, uma pasta de binários (fora da solution) e referencio tudo dela, fora da GAC. É uma questão de preferência de deploy. Eu gosto desta forma.

        Abraço,

        Eric

  2. Após fazer alguns ajustes, apresenta a mensagem abaixo. Vc pode me ajudar?

    Obs.: No Web Server, na pasta BIN tem a dll NHibernate.dll
    Server Error in ‘/sac’ Application.
    ——————————————————————————–

    Could not load file or assembly ‘NHibernate, Version=1.0.4.0, Culture=neutral, PublicKeyToken=154fdcb44c4484fc’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.IO.FileLoadException: Could not load file or assembly ‘NHibernate, Version=1.0.4.0, Culture=neutral, PublicKeyToken=154fdcb44c4484fc’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)

    Source Error:

    An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

    Assembly Load Trace: The following information can be helpful to determine why the assembly ‘NHibernate, Version=1.0.4.0, Culture=neutral, PublicKeyToken=154fdcb44c4484fc’ could not be loaded.

    WRN: Assembly binding logging is turned OFF.
    To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
    Note: There is some performance penalty associated with assembly bind failure logging.
    To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

    Stack Trace:

    [FileLoadException: Could not load file or assembly ‘NHibernate, Version=1.0.4.0, Culture=neutral, PublicKeyToken=154fdcb44c4484fc’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)]
    System.Signature._GetSignature(SignatureStruct& signature, Void* pCorSig, Int32 cCorSig, IntPtr fieldHandle, IntPtr methodHandle, IntPtr declaringTypeHandle) +0
    System.Signature.GetSignature(SignatureStruct& signature, Void* pCorSig, Int32 cCorSig, RuntimeFieldHandle fieldHandle, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle) +27
    System.Signature..ctor(RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle) +53
    System.Reflection.RuntimeConstructorInfo.get_Signature() +57
    System.Reflection.RuntimeConstructorInfo.GetParametersNoCopy() +24
    System.RuntimeType.FilterApplyMethodBaseInfo(MethodBase methodBase, BindingFlags bindingFlags, CallingConventions callConv, Type[] argumentTypes) +61
    System.RuntimeType.GetConstructorCandidates(String name, BindingFlags bindingAttr, CallingConventions callConv, Type[] types, Boolean allowPrefixLookup) +178
    System.RuntimeType.GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers) +30
    System.Type.GetConstructor(BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) +43
    Spring.Objects.Factory.Support.SimpleInstantiationStrategy.GetZeroArgConstructorInfo(Type type) +73
    Spring.Objects.Factory.Support.SimpleInstantiationStrategy.Instantiate(RootObjectDefinition definition, String name, IObjectFactory factory) +296
    Spring.Objects.Factory.Support.WebInstantiationStrategy.Instantiate(RootObjectDefinition definition, String name, IObjectFactory factory) +179
    Spring.Objects.Factory.Support.AbstractAutowireCapableObjectFactory.InstantiateObject(String objectName, RootObjectDefinition definition) +61
    Spring.Objects.Factory.Support.AbstractAutowireCapableObjectFactory.CreateObjectInstance(String objectName, RootObjectDefinition objectDefinition, Object[] arguments) +312
    Spring.Objects.Factory.Support.AbstractAutowireCapableObjectFactory.InstantiateObject(String name, RootObjectDefinition definition, Object[] arguments, Boolean allowEagerCaching, Boolean suppressConfigure) +873

    [ObjectCreationException: Error creating object with name ‘HibernateTemplate’ defined in ‘assembly [Equipe.Sac.Dao.Nhibernate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null], resource [Equipe.Sac.Dao.Nhibernate.Dao.xml] line 60’ : Initialization of object failed : Could not load file or assembly ‘NHibernate, Version=1.0.4.0, Culture=neutral, PublicKeyToken=154fdcb44c4484fc’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)]
    Spring.Objects.Factory.Support.AbstractAutowireCapableObjectFactory.InstantiateObject(String name, RootObjectDefinition definition, Object[] arguments, Boolean allowEagerCaching, Boolean suppressConfigure) +1374
    Spring.Objects.Factory.Support.AbstractObjectFactory.CreateAndCacheSingletonInstance(String objectName, RootObjectDefinition objectDefinition, Object[] arguments) +255
    Spring.Objects.Factory.Support.WebObjectFactory.CreateAndCacheSingletonInstance(String objectName, RootObjectDefinition objectDefinition, Object[] arguments) +570
    Spring.Objects.Factory.Support.AbstractObjectFactory.GetObjectInternal(String name, Type requiredType, Object[] arguments, Boolean suppressConfigure) +895
    Spring.Objects.Factory.Support.AbstractObjectFactory.GetObject(String name) +49
    Spring.Objects.Factory.Support.DefaultListableObjectFactory.PreInstantiateSingletons() +834
    Spring.Context.Support.AbstractApplicationContext.Refresh() +935
    Spring.Context.Support.WebApplicationContext..ctor(String name, Boolean caseSensitive, IApplicationContext parentContext, String[] configurationLocations) +94
    Spring.Context.Support.WebApplicationContext..ctor(String name, Boolean caseSensitive, String[] configurationLocations) +47
    (Object[] ) +220
    Spring.Reflection.Dynamic.SafeConstructor.Invoke(Object[] arguments) +48
    Spring.Context.Support.RootContextInstantiator.InvokeContextConstructor(ConstructorInfo ctor) +246
    Spring.Context.Support.ContextInstantiator.InstantiateContext() +195
    Spring.Context.Support.ContextHandler.InstantiateContext(IApplicationContext parentContext, Object configContext, String contextName, Type contextType, Boolean caseSensitive, String[] resources) +205
    Spring.Context.Support.WebContextHandler.InstantiateContext(IApplicationContext parent, Object configContext, String contextName, Type contextType, Boolean caseSensitive, String[] resources) +397
    Spring.Context.Support.ContextHandler.Create(Object parent, Object configContext, XmlNode section) +607

    [ConfigurationErrorsException: Error creating context ‘/sac’: Could not load file or assembly ‘NHibernate, Version=1.0.4.0, Culture=neutral, PublicKeyToken=154fdcb44c4484fc’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)]
    System.Configuration.BaseConfigurationRecord.EvaluateOne(String[] keys, SectionInput input, Boolean isTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult) +202
    System.Configuration.BaseConfigurationRecord.Evaluate(FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult, Boolean getLkg, Boolean getRuntimeObject, Object& result, Object& resultRuntimeObject) +1061
    System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject) +1431
    System.Configuration.BaseConfigurationRecord.GetSection(String configKey, Boolean getLkg, Boolean checkPermission) +56
    System.Configuration.BaseConfigurationRecord.GetSection(String configKey) +8
    System.Web.HttpContext.GetSection(String sectionName) +47
    System.Web.Configuration.HttpConfigurationSystem.GetSection(String sectionName) +39
    System.Web.Configuration.HttpConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String configKey) +6
    System.Configuration.ConfigurationManager.GetSection(String sectionName) +78
    Spring.Util.ConfigurationUtils.GetSection(String sectionName) +107
    Spring.Context.Support.WebApplicationContext.GetContextInternal(String virtualPath) +1388
    Spring.Context.Support.WebApplicationContext.GetRootContext() +147
    Spring.Context.Support.WebSupportModule.Init(HttpApplication app) +589
    System.Web.HttpApplication.InitModulesCommon() +65
    System.Web.HttpApplication.InitModules() +43
    System.Web.HttpApplication.InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers) +729
    System.Web.HttpApplicationFactory.GetNormalApplicationInstance(HttpContext context) +298
    System.Web.HttpApplicationFactory.GetApplicationInstance(HttpContext context) +107
    System.Web.HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr) +289

    ——————————————————————————–
    Version Information: Microsoft .NET Framework Version:2.0.50727.3603; ASP.NET Version:2.0.50727.4028
    ——————————————————————————–

  3. Parabéns. Excelente artigo!

    Explica de maneira clara e simples, o que é e como desenvolver aplicações pouco acopladas.

  4. Boa tarde Eric, estou estudando o framework Spring .net num projeto que estou desenvolvendo com o .net 4.0. Vc já fez algum projeto com essa versão do framework? Tem que configurar algum coisa a mais, mudar alguns métodos do Spring?

    1. Oi Deryck,

      Faz um tempinho que eu não estudo coisas novas do Spring.Net. Ainda não fiz nenhum projeto com .Net 4.0 e Spring.Net, mas acredito que não tenha segredo não.

      Abraço,

      Eric

  5. Boa Tarde Eric.

    Achei muito show de bola seu post, só que estou precisando adaptar este exemplo com NHibernate, será que tem como vc mandar algum exemplo ou postar no seu site.

    Desde de já agradeço pelo atenção.

    abc

    1. Alex,

      Infelizmente não tenho nenhum exemplo pronto não, viu?

      Mas acredito que junto com o próprio Spring.Net, deve ter.

      Abraço,

      Eric

  6. Olá Eric tenho 1 dúvida.

    Não entendi o paragrafo que você mencionou abaixo.
    “Por último, dizemos que todos os objetos do tipo “~/Default.aspx”, terão sua propriedade pedidoBO, automaticamente atribuida para nossa instância chamada PedidoBO no contexto.”

    Sou obrigado a fazer sempre essa configuração no web.config para os objetosBO serem injetados nas páginas ?
    Se 1 página tiver que acessar 3 objetos BOs diferentes, terei que fazer todas as configurações ?
    Imagina um sistema gigante, se tiver que fazer isso em todos as páginas o web.config ficará enorme.
    Não podemos instanciar os objetos BO normalmente sem a necessidade de configuração no web.config ?
    Exemplo:
    page_Load()
    {
    PedidoBO pedido = new PedidoBO();

    }

    Obrigado,
    Carlos

    1. Carlos,

      A resposta é sim. Para cada “BO” diferente, tem que fazer uma configuração equivalente.

      Se você instanciar diretamente da página como sugeriu, sua página conhecerá a classe PedidoBO. O objetivo de ter um container de injeção de dependência é justamente evitar esse acoplamento. Obvio que vale a pena analisar quando existem benefícios disso.

      Se vc partir pra pras próximas partes do tutorial, vai perceber o conceito de AOP. Aí realmente o custo todo se paga.

      Abraço,

      Eric

  7. Olá Eric,

    Excelente post, mas me surgiu uma dúvida.
    Existe alguma forma de configurar o proxy de composição apontando para qualquer outro tipo de aplicação que não seja web?

    Abraços!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s