Inversão de Controle - IoC Tópicos (Avançados) de Programação Orientada a Objetos Prof. Fabio Kon DCC - IME - USP Diego Tarábola e Denise Goya outubro/2006 Copyleft by Diego Tarábola e Denise Goya 1 Inversão de Controle - IoC Visão Geral Princípio de Hollywood: “Não nos ligue, nós ligaremos”. Dependência de componentes. Dependência de componentes também conhecida como colaboradores de objetos. O objeto que exige dependência é conhecido como objeto dependente. Arcabouço é responsável pela execução da operação. Ocorre em tempo de execução. Copyleft by Diego Tarábola e Denise Goya 2 Inversão de Controle — Motivação Cenário 1 Classe A precisa de uma referência para a Classe B. Classe B é uma classe concreta que tem um construtor padrão. Classe A possui uma instância de B. Nenhuma outra classe pode acessar uma instância da Classe B [Mal06]. public class A{ private B b; public A(){ b=new B(); } } 1 - diagrama UML de seqüência Copyleft by Diego Tarábola e Denise Goya 3 Inversão de Controle — Motivação Cenário 2 Objeto a possui referência para os objetos c e b. public class A{ private B b; public A(){ C c=new C(); b=new B(c); } } 2 - diagrama UML de seqüência Copyleft by Diego Tarábola e Denise Goya 4 Inversão de Controle — Motivação Cenário 3 A precisa de uma referência para B, e não precisa saber como B é instanciado. B pode ser uma interface, uma classe abstrata ou concreta. Antes de instanciar a classe A, precisa de uma referência para a classe B. public class A{ private B b; public A(){ } public setB(B b){ this.b=b; } } 3 - diagrama UML de seqüência Copyleft by Diego Tarábola e Denise Goya 5 Tipos de Inversão de Controle Inversão de Controle Procura por Dependência Contextualized Dependency Lookup Injeção de Dependência Dependency Pull Interface Copyleft by Diego Tarábola e Denise Goya Setter Construtor 6 Tipos de Inversão de Controle Inversão de Controle (IoC) possui dois tipos [Har05]: Injeção de Dependência (Dependency Injection) Procura por Dependência (Dependency Lookup) Injeção de Dependência sempre diz respeito a IoC, mas IoC nem sempre referencia Injeção de Dependência Copyleft by Diego Tarábola e Denise Goya 7 Tipos de Inversão de Controle Procura por Dependência: um componente deve obter uma referência para uma dependência; dois subtipos: Dependency Pull Contextualized Dependency Lookup (CDL) Injeção de Dependência: as dependências são literalmente injetadas (incluídas) três subtipos: Interface – Tipo 1 Setter – Tipo 2 Constructor – Tipo 3 Copyleft by Diego Tarábola e Denise Goya 8 Contêineres de Inversão de Controle Spring Excalibur: Fortress Avalon Resin PicoContainer Copland (Ruby) HiveMind Mentawai Copyleft by Diego Tarábola e Denise Goya 9 Procura por Dependência — Dependency Pull As dependências são obtidas através de um registro assim que necessário; Mecanismo para encontrar componentes que gerenciam o arcabouço. Procura por Dependência com Spring public static void main(String[] args) throws Exception { // get the bean factory BeanFactory factory = getBeanFactory(); MessageRenderer mr = (MessageRenderer) factory.getBean("renderer"); mr.render(); } Copyleft by Diego Tarábola e Denise Goya 10 Procura por Dependência — Contextualized Dependency Lookup (CDL) Semelhante ao Dependency Pull; Procura é execuada pelo contêiner que está gerenciando o recurso e não a partir de um registro central; CDL funciona através da implementação de uma interface. Interface do Componente para CDL com Spring public interface ManagedComponent { public void performLookup(Container container); } Copyleft by Diego Tarábola e Denise Goya 11 Procura por Dependência — Contextualized Dependency Lookup (CDL) Executa o método performLookup() implementado pelo componente; O componente pode procurar sua dependência utilizando a interface Container. Obtendo dependência com o CDL public class ContextualizedDependencyLookup implements ManagedComponent { private Dependency dep; public void performLookup(Container container) { this.dep = (Dependency) container.getDependency("myDependency"); } } Copyleft by Diego Tarábola e Denise Goya 12 Injeção de Dependência — por meio de Construtor Dependência do componente é disponibilizada através de seu(s) construtor(es); O argumento recebido será sua dependência. Injeção de Dependência pelo Construtor com Spring public class ConstructorInjection { private Dependency dep; public ConstructorInjection(Dependency dep) { this.dep = dep; } } Copyleft by Diego Tarábola e Denise Goya 13 Injeção de Dependência — por meio de método Setter O contêiner IoC injeta um componente de dependência através de seu método set (JavaBean); O componente setter permite um conjunto de dependências que o contêiner de inversão de controle pode gerenciar. Injeção de Dependência através do método Setter com Spring public class SetterInjection { private Dependency dep; public void setMyDependency(Dependency dep) { this.dep = dep; } } Copyleft by Diego Tarábola e Denise Goya 14 Injeção de Dependência — por meio de Interface [Fow06] Componente de dependência através de uma interface; A interface InjectFinder será definida por qualquer um que fornecer a interface MovieFinder (no exemplo abaixo). Injeção de Dependência através de Interface. Exemplo no arcabouço Avalon public interface InjectFinder { public void injectFinder(MovieFinder finder); } public class MovieLister implements InjectFinder { public void injectFinder(MovieFinder finder){ this.finder = finder; } Copyleft by Diego Tarábola e Denise Goya 15 Injeção de Dependência — por meio de Interface Exemplo: Classe de Teste no Avalon: configura os componentes e os injetores. public class Tester { private Container container; private void configureContainer(){ container = new Container(); registerComponents(); registerInjectors(); container.start(); } } Copyleft by Diego Tarábola e Denise Goya 16 Injeção de Dependência — por meio de Interface É preciso registrar os injetores que irão injetar os componentes dependentes; Cada interface de injeção precisa de algum código para injetar o objeto dependente; No código abaixo, registram-se os objetos injetores no contêiner: private void registerInjectors(){ container.registerInjector(InjectFinder.class, container.lookup(“MovieFinder”)); } Copyleft by Diego Tarábola e Denise Goya 17 Injeção versus Procura O tipo de IoC é definido pelo contêiner adotado. Com Dependency Pull, é necessário: registrar dependências; obter referências das dependências; interagir com as dependências obtidas. Utilizando CDL, é preciso: que as classes implementem uma interface específica; e procure por todas dependências manualmente. Com Injeção de Dependência, as classes precisam: permitir que as dependências sejam injetadas por meio de construtores, métodos setters ou interfaces. Copyleft by Diego Tarábola e Denise Goya 18 Contêiner de Inversão de Controle Arcabouços incluem contêineres de IoC; Arcabouços manifestam inversão de controle em tempo de execução, por meio de retorno de chamadas [SGN04] ; Chamadas invocam métodos hook de componentes definidos pela aplicação, sempre que ocorre algum evento; Na ocorrência de um evento, o arcabouço chama de volta um método virtual num componente de aplicação pré-registrado; O componente executa o processamento definido pela aplicação, em resposta ao evento; O controle é alternado entre o arcabouço e a aplicação. Copyleft by Diego Tarábola e Denise Goya 19 Contêiner de Inversão de Controle Exemplo: Recebimento de chamada telefônica [POSA 2] Uma companhia de telefone possui um mecanismo IoC; Um usuário: um tratador de evento, que está registrado junto à companhia, para tratar as chamadas ao número desse usuário. Quando alguém chama pelo número daquele usuário, a rede notifica o tratador que um evento de requisição de chamada está pendente (via toque do telefone); Assim que o telefone é tirado do gancho, existe uma reação a esta requisição (o tratador assume o controle), iniciando uma conversação entre as partes conectadas. Copyleft by Diego Tarábola e Denise Goya 20 Ex.: Injeção de Dependência com Spring Bean: qualquer componente gerenciado pelo contêiner; configuração do bean: armazena informações próprias e do bean do qual depende; representada por instâncias de classes que implementam a interface BeanDefinition; Interface BeanFactory: responsável por gerenciar componentes no contêiner; qualquer aplicação interage com o Spring por meio dessa interface; a aplicação deve: criar uma instância da classe que implemente essa interface; configurar com a informação de dependência. Copyleft by Diego Tarábola e Denise Goya 21 Ex.: Injeção de Dependência com Spring arquivo de configuração para BeanDefinition: classe PropertiesBeanDefinitionReader ou XmlBeanDefinitionReader private static BeanFactory getBeanFactory() throws Exception { // obtém a fábrica do bean DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // cria uma definição do leitor PropertiesBeanDefinitionReader rdr = new PropertiesBeanDefinitionReader(factory); // lê as opções de configuração Properties props = new Properties(); props.load(new FileInputStream("./src/conf/beans.properties")); rdr.registerBeanDefinitions(props); return factory; } Copyleft by Diego Tarábola e Denise Goya 22 Ex.: Injeção de Dependência com Spring A informação de BeanDefinition é obtida através de um arquivo de propriedades (properties). Uma vez criada e configurada a implementação de BeanFactory, o bean é encontrado utilizando seu nome, que está configurado no arquivo de propriedades. public static void main(String[] args) throws Exception { // get the bean factory BeanFactory factory = getBeanFactory(); MessageRenderer mr = (MessageRenderer) factory.getBean("renderer"); mr.render(); } Copyleft by Diego Tarábola e Denise Goya 23 Ex.: Injeção de Dependência com Spring Com XmlBeanDefinitionReader, é possível gerenciar a configuração do bean utilizando XML em vez de propriedades: DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader rdr = new XmlBeanDefinitionReader(factory); rdr.loadBeanDefinitions(new FileSystemResource("src/conf/beans.xml")); Oracle oracle = (Oracle)factory.getBean("oracle"); XmlBeanFactory é derivada de DefaultListableBeanFactory que estende a configuração utilizando XmlBeanDefinitionReader XmlBeanFactory factory = new XmlBeanFactory( new FileSystemResource("ch4/src/conf/beans.xml")); Oracle oracle = (Oracle)factory.getBean("oracle"); Copyleft by Diego Tarábola e Denise Goya 24 Ex.: Injeção de Dependência com Spring Configuração do bean: O ponto chave para qualquer aplicação baseada no Spring é configurar um arquivo para sua aplicação. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> </beans> Cada bean é definido com a tag <bean>, dentro de <beans> <bean> possui dois atributos: <id> utilizado para obter seu nome padrão <class> especifica o tipo do bean Copyleft by Diego Tarábola e Denise Goya 25 Ex.: Injeção de Dependência com Spring Exemplo de configuração com XML: <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="renderer" class="com.apress.prospring.ch2.StandardOutMessageRenderer"/> <bean id="provider" class="com.apress.prospring.ch2.HelloWorldMessageProvider"/> </beans> Copyleft by Diego Tarábola e Denise Goya 26 Ex.: Injeção de Dependência com Spring Lendo configuração do XML: public class HelloWorldXml { public static void main(String[] args) throws Exception { // get the bean factory BeanFactory factory = getBeanFactory(); MessageRenderer mr = (MessageRenderer)factory.getBean("renderer"); MessageProvider mp = (MessageProvider)factory.getBean("provider"); } mr.setMessageProvider(mp); mr.render(); private static BeanFactory getBeanFactory() throws Exception { // get the bean factory XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("ch4/src/conf/beans.xml")); return factory; } } Copyleft by Diego Tarábola e Denise Goya 27 Ex.: Injeção de Dependência com Spring Usando Setter Injection A tag <property> especifica a injeção de dependência; Para relacionar o bean “provider” a messageProvider do bean “renderer” <bean id="renderer” class="com.apress.prospring.ch2.StandardOutMessageRenderer"> <property name="messageProvider"> <ref local="provider"/> </property> </bean> A tag <ref> relaciona uma referência do bean à propriedade; Não é mais preciso utilizar o método set para injetar as dependências. Copyleft by Diego Tarábola e Denise Goya 28 Ex.: Injeção de Dependência com Spring Configuração de Injeção de Dependência com XML: public class HelloWorldXmlDI { public static void main(String[] args) throws Exception { // get the bean factory BeanFactory factory = getBeanFactory(); MessageRenderer mr = (MessageRenderer)factory.getBean("renderer"); } mr.render(); private static BeanFactory getBeanFactory() throws Exception { // get the bean factory XmlBeanFactory factory = new XmlBeanFactory( new FileSystemResource("ch4/src/conf/beans.xml")); return factory; } } Copyleft by Diego Tarábola e Denise Goya 29 Ex.: Injeção de Dependência com Spring Usando Constructor Injection A tag <constructor-arg> especifica a injeção de dependência, em vez da tag <property>; É passada uma String (e não mais um bean); Utiliza-se a tag <value> em vez da tag <ref> para especificar o valor do argumento do construtor; <bean id="provider" class="com.apress.prospring.ch4.ConfigurableMessageProvider"> <constructor-arg> <value> This is a configurable message </value> </constructor-arg> </bean> Copyleft by Diego Tarábola e Denise Goya 30 Ex.: Injeção de Dependência com Spring Configuração de Injeção de Dependência com XML: public class ConfigurableMessageProvider { private String message; public ConfigurableMessageProvider(String message){ this.message = message; } public String getMessage(){ return message; } } Para passar mais de um argumento ou para utilizar mais de um construtor deve-se usar um índice, iniciando-se em 0 na tag <constructor-arg>. Copyleft by Diego Tarábola e Denise Goya 31 Inversão de Controle e Padrão Reactor Reactor: Padrão de Arquitetura [POSA 2]; Conhecido também como Dispatcher, Notifier Contexto: Aplicação orientada a eventos que processa as informações de forma síncrona e serial. Problema: aplicações devem: atender muitas requisições simultaneamente, demultiplexar e despachar as indicações de eventos às respectivas implementações de serviço. Copyleft by Diego Tarábola e Denise Goya 32 Inversão de Controle e Padrão Reactor Define uma interface que permite que aplicações: registrem ou removam tratadores de eventos; sejam executadas no laço de eventos da aplicação. Reactor utiliza um demultiplexador síncrono de evento para aguardar pela indicação de eventos que se originam de uma ou mais fontes. Quando ocorre um evento: o demultiplexador síncrono de evento notifica o Reactor; o Reactor aciona o tratador associado, para realização do serviço solicitado. O Reactor inverte o fluxo de controle, pois é responsabilidade dele (e não da aplicação) em disparar os tratadores concretos para cada tipo de evento. Copyleft by Diego Tarábola e Denise Goya 33 Desvantagens na Inversão de Controle Dificuldade na integração de arcabouços: quando dois ou mais arcabouços chamam simultaneamente o código da aplicação, cada qual pressupondo seu próprio fluxo de controle; Dificuldade na depuração do código e compreensão do fluxo: o controle é alternado entre o código da aplicação e o código do arcabouço. Copyleft by Diego Tarábola e Denise Goya 34 Referências [Fow06] Fowler, Martin. Inversion of Control Containers and Dependency Injection Pattern. Disponível em: http://www.martinfowler.com/articles/injection.html, acesso em 30/08/2006. [Har05] Harrop, Rob. Machacek, Jan. Pro Spring. Capítulo 4, páginas 49 a 92. Apress, 2005. [Mal06] Malarvannan Mani. Design Better Software with the Inversion of Control Pattern. Disponível em: http://www.devx.com/Java/Article/27583, acesso em 20/09/2006 [POSA 2] Schmidt, Douglas. Stal, Michael. Rohnert, Hans. Buschmann, Frank. Pattern-Oriented Software Architecture: Patterns for Concurrent and Networked Objects. Volume 2. Capítulo 3, páginas 175 a 214. Ed. John Wiley & Sons, 2000. [SGN04] Schmidt, Douglas. Gokhale, Aniruddha. Natarajan, Balachandran. Leveraging Application Frameworks. ACM Queue Magazine, Vol2, Nº 5, jul-ago/2004. Copyleft by Diego Tarábola e Denise Goya 35