410 likes | 678 Views
Spring Framework IoC - Inversão de Controle. Jobson Ronan {jrjs@cin.ufpe.br}. Motivação. Desenvolver uma aplicação sempre impõe desafios Muitos deles já foram solucionados por outros esforçados “guerreiros” Criaram padrões... Padrões de projeto... Design patterns.
E N D
Spring Framework IoC - Inversão de Controle Jobson Ronan {jrjs@cin.ufpe.br}
Motivação • Desenvolver uma aplicação sempre impõe desafios • Muitos deles já foram solucionados por outros esforçados “guerreiros” • Criaram padrões... Padrões de projeto... Design patterns
Alguns padrões muito comuns • Facade • Singleton • Factory • Abstract Factory • Command • Adapter • Decorator • Service Locator • Prototype • ...
Design e Manutenibilidade • Sistemas manuteníveis • Evoluem bem com mudanças e iterações • Chave do segredo: Gerenciar dependências • Outros aspectos importantes • Design por contrato • Definição limpa do contrato das classes/interfaces • Contrato: O que faz, como se comporta • Não como está implementado! • Testes unitários • Escrever testes que verificam o contrato da classe/interface • Separação de interesses • Escrever uma classe/interface para cada interesse
Dependência • É quando se prende um componente a outro por: • Herança • Composição • Instanciação • Instanciação por factory • Parâmetro de método • Uso de métodos ou atributos estáticos
Princípios de desenvolvimento ágil • Quando uma Componente possui uma dependência, sua implementação pode precisar mudar quando • Os requisitos mudarem, ou... • Quando sua dependência mudar • Abstrações tendem a ser estáveis • Interfaces estão normalmente pouco sujeitas a serem alteradas • Componentes concretos tendem a ser instáveis • Classes concretas normalmente estão mais sujeitas a terem sua implementação alterada
public classUserManager { privateUserDao userDao; } UserManager UserDao UserManager depende de UserDao Dependência por Composição • Composição cria um fraco acoplamento • Quando o comportamento ou a interface da dependência (UserDao) muda, apenas o dependente precisa se adaptar • Não Possui os efeitos colaterais de herança <<use>>
Dependência por Instanciação WebServerProbe depende de Socket WebServerProbe public class WebServerProbe { public boolean isRunning() { Socket socket = new Socket(); ... <<use>> Socket
Dependência por factory UserManager UserManager depende de UserDaoFactory (UserManager também depende de UserDao) public class UserManager { public Collection getUsers() { UserDAO userDAO = UserDAOFactory.newInstance(); ... <<use>> UserDaoFactory <<use>> <<create>> UserDao
Dependência por parâmetro de metodo UserManager depende de Usuário • Mesmo acoplamento fraco obtido por composicao UserManager public class UserManager { public void saveUser(User user) { ... <<use>> User
Dependência por uso de método estático WebServerProbe depende de Arrays WebServerProbe public class WebServerProbe { private Collection ports; public void setPorts(Integer[] ports) { this.ports = java.util.Arrays.asList(ports); ... <<use>> Arrays
Dependências • Algumas formas de dependência são piores que outras • Herança: Quando novos métodos são adicionados e usados, os componentes não recebem nenhum aviso para sobrescreve-los • Prefira Composição • Classes, métodos e atributos estáticos (ex: Singletons): dependências ficam escondidas nos componentes dependentes, tornando difícil a alteração dos dependentes quando componentes estáticos são alterados • Usar inversão de controle
Dependências • Dependências não são um mal • Claro que sempre existirão dependências. • O objetivo é • Minimizar o número de dependências no seu modelo • Depender apenas de interfaces • Deve-se primar por • Componentes fracamente acoplados • Depender de interfaces e não de classes, o máximo possível • Quanto menos sua aplicação estiver sujeita a mudanças, mais estável e manutenível estará
“Fracamente acoplados” • Acoplamento forte • Instanciação de classes concretas dentro da lógica de negócio • Uso de métodos estáticos • Herança • Acoplamento fraco • Usando Factories e ServiceLocator • Usando inversão de controle InputStream is = new FileInputStream(...); InputStream is = StreamUtils.getConfigFileInputStream(); InputStream is = new ConfigFactory().createInputStream();
Inversão de controle • ...Mas primeiro, sem IoC
Implementação sem IoC • O próprio componente precisa obter suas dependências • Ex: JNDI, EJB Stub, JDBC Connection, arquivos de propriedades, etc. • Desacoplável por: • Codificar por interfaces • Obter esses objetos usando um Factory ou um ServiceLocator • Efetuar-se chamadas a esses objetos, potencialmente para obter outras dependências • Ex: JNDI, JDBC Connection Pool, etc. • Encadeia dependências
Conseqüências • Código cheio de código específico de obtenção a dependências • Instanciação cria acoplamento • Código ligado a um determinado ambiente (container) (EJB Container, Servlet Container, Rich client, ...) • Dificulta a implementação de testes unitários
Código específico de obtenção a dependências • As vezes, mais código para isso que para a implementação da lógica de negócio • Solução clássica: encapsular > Service locator, Factory • Problema: Escrever os Factories • Problema: Singletons are evil • Dependências não muito claras, atravessam todo o código • Dificultam unit testing • Dificultam Refactories
Codificação por interface desacopla, mas... • A instanciação ainda usa uma implementação concreta (=> acoplamento) • Solução clássica: usar uma Factory • Problema: • forte acoplamento com a Factory • Torna difícil a manutenção • Torna difícil a implementação de Testes Unitários
Código ligado a um determinado ambiente • Código de recuperação de dependências dentro da lógica de negócio • Ambiente/container variados • Standalone Java Application • Swing/SWT Java Application • Dentro de um container EJB • Dentro de um servlet container • Exemplo: Obtenção de um objeto Connection (JDBC) • standalone: usando java.sql.DriverManager • standalone com pool: usando Apache Commons DBCP • Tomcat: usando InitialContext e DataSource • ...
Dificulta a implementação de testes unitários • Porque o “como” de obter uma dependência está codificado dentro da classe que se quer testar • Ex: Usando um Factory, como você pode mudar seu comportamento de acordo com um teste unitário? • Agora... Como testar MyLogic sem EJB e precisando de um EJB container? class MyLogic { public void doSomething() { // use the InitialContext (JNDI) Service Locator // to retrieve the “BusinessLogic” object InitialContext ctx = new InitialContext(); BusinessLogic partner = (BusinessLogic) ctx.lookup(“my/business/logic/impl”); // now perform the actual logic: partner.doYourOwnBusiness(); } }
Inversão de controle • ...E agora com IoC
Inversão de controle • Um meio de por os componentes juntos • Define-se • Interfaces e implementações • Dependências entre classes e interfaces (transformando-as em “colaboradores”) • O Container de Inversão de controle • Cria o dependente e a dependência, e injeta esta ultima no dependente • Possibilita selecionar que implementação de dependência injetar em cada dependente (por configuração, código, automática)
Inversão de controle • Arquitetura do container leve • Usa POJOs • Sem necessidade de deploy em um container pesado • Aumenta testabilidade • Não é intrusiva • Não depende de nenhuma API especifica do container • Sem interfaces para implementar, sem classes para herdar, exceto as suas
IoC - Princípios • “Hollywood principle” • “Don´t call me, i´ll call you” • Sem IoC, componentes lógicos tem o controle sobre suas dependências e,por conseguinte,devem obtê-las • Com IoC, a lógica dos componentes não tem controle sobre suas dependências e não as obtém • Um container de IoC irá injetar as dependências nos objetos (Dependency Injection)
Principais vantagens • Efetivamente desacopla componentes lógico de suas dependências • Remove o código de obtenção de dependências dos dependentes (Agora é trabalho do container). • Melhora o design do modelo • Aumenta a flexibilidade e o reuso de componentes • Testes unitários ficam mais fáceis • Não depende de ambientes específicos
Exemplos • Sem inversão de controle class MyLogic { private BusinessServiceInterface businessService = null; protected final BusinessServiceInterface getBusinessService() { if (businessService == null) { // retrieve JNDI InitialContext Context ctx = new InitialContext(); Context env = (Context) ctx.lookup(“java:comp/env”); Object obj = env.lookup(“ejb/BusinessServiceHome”); // retrieve EJB stub BusinessServiceHome businessServiceHome = (BusinessServiceHome) PortableRemoteObject.narrow( env, BusinessServiceHome.class); businessService = businessServiceHome.create(); } return businessService; } public void doYourThing() { getBusinessService().doYourBusiness(); } }
Exemplos • Sem inversão de controle • Tenta testar isso! • Terá que re-implementar quando BusinessService não for mais um EJB • Terá que re-implementar quando não estiver em um EJB container (Sem JNDI) • Terá que re-implementar quando não quiser mais um cache do Stub
Exemplos • Sem IoC, com ServiceLocator: • Estratégia de ciclo de vida centralizado para BusinessServiceInterface • Dependência centralizada com o container EJB • Testes unitários ainda difíceis (precisa alterar o comportamento de MyService) • Se diferentes classes precisarem de objetos BusinessServiceInterface de diferentes fontes? Como cuidar disso? • Você terá um não manutenível número de Services Locators, ou catastróficos efeitos colaterais ao alterar a implementação de MyService protected final BusinessServiceInterface getBusinessService(){ if (businessService == null) { businessService = MyServices.getBusinessService(); } return businessService; }
Exemplos • Com IoC • Sem dependência em como o BusinessService é obtido • Sem código de obtenção dedependêcia, apenas um atributo e um setter • Depende apenas da interface BusinessServiceInterface class MyLogic { // businessService will be set by the IoC container: private BusinessServiceInterface businessService; // we'll use setter-based injection (explained later): public void setBusinessService(BusinessServiceInterface businessService) { this.businessService = businessService; } public void doYourThing() { // perform call on businessService businessService.doYourBusiness(); } }
Exemplos • Sem inversão de controle
Exemplos • Sem IoC, com ServiceLocator
Exemplos • Com IoC
Mas como funciona?? class MyMain { public static void main(String[] args) { // initialize the IoC container (here it's Spring): XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource(“beans.xml”)); // retrieve MyLogic: MyLogic myLogic = (MyLogic) xmlBeanFactory.getBean(“myLogic”); // call the method: myLogic.doYourThing(); } } <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans ...> <!-- this is beans.xml --> <beans> <bean id=”theBusinessSvc” class=”foo.BusinessServiceMock”/> <bean id=”myLogic” class=”foo.MyLogic”> <property name=”businessService”> <bean ref=”theBusinessSvc”/> </property> </bean> </beans>
Conclusões • O princípio da inversão de controle pode ser aplicado elegantemente com o Spring • Isto irá reduzir em grande quantidade a quantidade de padrões de projeto aplicados, simplificando o design do modelo • Isto está mudando a forma de como desenvolver aplicações “Precisamos de novos Design Patterns,os que conheciamos não são mais necessários.” Jobson Ronan
Tipos de injeção de dependência • Tipo 1: Interface-based injection • Tipo 2: Setter-based injection • Tipo 2: Contructor-based injection
Exercício • Criar aplicação completa de reservas de videos • DAOs + Fachada • Não é necessário classes de cadastro • Extrair dependências possuídas pela fachada a DAOs concretos. • A fachada deve apenas conhecer a interface dos DAOs • Use o springframework para injetar as dependências
Spring Framework IoC - Inversão de Controle Jobson Ronan {jrjs@cin.ufpe.br}