Injeção de dependência
A injeção de dependência é um design pattern que visa desacoplar os componentes da aplicação.
Os componentes são instanciados externamente a classe. Um gerenciador controla essas instancias.
Os componentes tem dependencias entre si. Esse gerenciador, através de uma configuração, liga os componentes
de forma a montar a aplicação. Por exemplo:
public class ExecutaQuery {
private Conexao conexao;
public void setConexao(Conexao conexao){
this.conexao = conexao;
}
public Conexao getConexao(){
return conexao;
}
(...)
}
public class Conexao {
(...)
}
Suponhamos uma classe ExecutaQuery que execute queries em um banco de
dados. Para executar uma query uma conexão com o banco de dados é necessária. Essa conexão é
representada pela classe Conexao.
A classe ExecutaQuery possui um setter para conexao,
ela precisa de uma conexão com o banco de dados para executar as queries. Em um ambiente normal
o código para criar e configurar seria como o exemplo:
ExecutaQuery executaQuery = new ExecutaQuery();
Conexao conexao = new Conexao();
executaQuery.setConexao(conexao);
Para uma pequena aplicação esse código é simples. Mas em aplicações grandes com várias classes e várias
dependências isso se torna bem complexo. O framework faz exatamente o que o código acima faz, mas
o programador não precisa instanciar e nem configurar esses beans.
Um framework de injeção de dependencias irá criar e configurar os beans sem a necessidade de programação.
O NEO utiliza para a injeção de dependências o framework Spring
(
www.springframework.org). O Spring
possui uma fábrica que gerencia a criação e configuração dos beans. O NEO registra as classes nessa
fábrica para a criação e configuração desses objetos. O NEO possui uma forma de registro de classes que
dispensa o uso de XML, mas ainda é possível registrar os beans através do XML do Spring.
Configurando beans com annotations (@Bean)
Para utilizar a injeção de dependências é necessário uma fábrica que crie e configure os objetos da aplicação.
Para a criação dessa fábrica é necessário a utilização de um listener
(br.com.linkcom.neo.core.web.init.ContextLoaderListener)
que deve ser configurado no web.xml. Esse listener cria e configura a
fábrica do Spring. Essa classe extende o ContextLoaderListener do Spring.
Portanto, qualquer configuração aplicável ao listener do Spring é também aplicavel ao NEO.
Exemplo de web.xml com o listener:
[JSP]
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
[ATTR]http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<listener>
<listener-class>br.com.linkcom.neo.core.web.init.ContextLoaderListener</listener-class>
</listener>
</web-app>
Com o listener configurado podemos colocar beans na fábrica do Spring. O NEO permite a utilização de
anotations para a configuração desses beans. Para configurar um bean para a fábrica basta anotar a
classe com @Bean. Vamos voltar ao exemplo do
ExecutaQuery e Conexao para ver
como a configuração desses beans seria feita no NEO.
import br.com.linkcom.neo.bean.annotation.Bean;
@Bean
public class ExecutaQuery {
private Conexao conexao;
public void setConexao(Conexao conexao){
this.conexao = conexao;
}
public Conexao getConexao(){
return conexao;
}
(...)
}
import br.com.linkcom.neo.bean.annotation.Bean;
@Bean
public class Conexao {
(...)
}
O NEO detecta a presença da anotação @Bean e registra as classe
na fábrica do Spring. Como a classe
ExecutaQuery possui um setter para
Conexao, ele será chamado passando como parâmetro a instancia de
Conexao que foi criada. Apenas uma instancia de cada
classe é criada, mesmo que mais de um objeto tenha dependencia de uma mesma classe. Os objetos no NEO
são criados utilizando o design pattern singleton.
Note que, se a classe Conexao não estivesse anotada com
@Bean nenhum bean da classe Conexao
seria criado e o método setConexao(...) de
ExecutaQuery não seria chamado.
Importante: Apenas as classes que estiverem dentro de /WEB-INF/classes serão registradas.
O NEO não lê as classes que estiverem dentro de pacotes JAR dentro do lib da aplicação.
Para buscar os beans registrados na aplicação existem duas formas.
Utilizando a factory do Spring:
WebApplicationContext webApplicationContext = NeoWeb.getWebApplicationContext(servletContext);
DefaultListableBeanFactory factory = webApplicationContext.getConfig().getDefaultListableBeanFactory();
ExecutaQuery executaQuery = (ExecutaQuery) factory.getBean("executaQuery");
Buscando pelo NEO:
NeoWeb.getWebApplicationContext(servletContext);
ExecutaQuery executaQuery = Neo.getObject(ExecutaQuery.class);
O método getWebApplicationContext(...) é utilizado apenas para
inicializar o contexto do NEO.
O NEO possui dois contextos: de aplicação e de requisição. O contexto de aplicação
é equivalente ao contexto de aplicação do J2EE e o contexto de requisição é equivalente
ao contexto de requisição do J2EE. A fábrica do Spring é guardada no escopo de aplicação
por isso é necessário inicializar o contexto de aplicação antes de buscar algum bean da fábrica.
Existe uma outra forma de utilizar o contexto NEO sem a necessidade de criá-lo explicitamente.
Para utilizar essa forma é necessário cadastrar um filtro no web.xml:
[JSP]
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
[ATTR]http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<listener>
<listener-class>br.com.linkcom.neo.core.web.init.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>neoFilter</filter-name>
<filter-class>br.com.linkcom.neo.core.web.NeoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>neoFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Com esse filtro, para utilizar um bean gerenciado basta utilizar o seguinte código:
ExecutaQuery executaQuery = Neo.getObject(ExecutaQuery.class);
Passando apenas a classe desejada o NEO retorna um objeto configurado.
Nesse caso, a chamada:
executaQuery.getConexao();
Retornaria uma instancia de Conexao. Já que ela foi configurada através
da injeção de dependencias em ExecutaQuery.
Configurando relacionamentos
Apenas o fato de colocar um setter para uma propriedade faz com que o NEO
configure automaticamente a classe. Mas é possível escolher qual bean utilizar para configurar
determinada propriedade.
A escolha de qual bean configurar em determinada propriedade pode ser útil em alguns casos.
Vamos entender como funciona a fábrica do Spring.
A fábrica do Spring contém uma coleção de todas as classes e configurações para instanciar os beans.
Cada configuração dentro dessa fábrica representa um bean. Uma configuração deve definir no mínimo um nome
e uma classe. É possível ter, dentro de uma fábrica, vários beans de uma mesma classe. Mas cada configuração
deve ter um nome diferente. Por isso, o que identifica um bean dentro da fábrica é o seu nome.
Quando utilizamos a anotação @Bean o nome que a classe é registrada no
Spring é o nome simples da classe (sem o pacote) com a primeira letra minúscula. Se temos uma classe
a.b.c.MinhaClasse anotada com @Bean
o nome que essa classe será registrada será 'minhaClasse'.
A anotação @Bean permite que o nome de registro do bean seja alterado
através da propriedade name. Veja o exemplo:
import br.com.linkcom.neo.bean.annotation.Bean;
@Bean(name="c")
public class Conexao {
}
Agora para conseguir um bean de Conexao o nome informado deve ser 'c'. Exemplo:
DefaultListableBeanFactory factory = Neo.getApplicationContext().getConfig().getDefaultListableBeanFactory();
Conexao conexao = (Conexao) factory.getBean("c");
Não é possível utilizar o método resumido do NEO (Neo.getObject(...))
para buscar beans cujo nome não seja o nome da classe com a primeira letra minúscula.
Dica: Utilize a anotação @Bean sem a propriedade
name, isso faz com que o código fique mais limpo
e seja possível utilizar a forma resumida do NEO para buscar os beans.
Para alterar o mapeamento de alguma propriedade utilizamos a anotação
@Ref no setter da propriedade.
Exemplo:
import br.com.linkcom.neo.bean.annotation.Bean;
@Bean
public class ExecutaQuery {
private Conexao conexao;
@Ref("c")
public void setConexao(Conexao conexao){
this.conexao = conexao;
}
public Conexao getConexao(){
return conexao;
}
(...)
}
Nesse caso estamos configurando a propriedade conexao com o bean 'c'. Se não existir o bean 'c' na fábrica
do Spring uma exeção será lançada ao iniciar a aplicação.
Configurando beans através de XML
Apesar de não ser necessário, a configuração de beans pode também ser feita através do XML do
Spring. No neo o xml padrão é o applicationConfig.xml. Esse xml deve ficar dentro da pasta /WEB-INF/ da aplicação.
Importante: No Spring o xml de configuração padrão é applicationContext.xml no Neo é applicationConfig.xml
Exemplo de XML de configuração:
[JSP]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
.
.
.
</beans>
Esse XML pode ser útil para fazer configurações de banco de dados e envio de e-mail por exemplo.
A configuração desse XML é igual a configuração de um XML do Spring que não esteja utilizando o NEO.
Para mais informações sobre a configuração via XML do Spring visite o site
www.springframework.org
Organização da aplicação
Em uma aplicação profissional é importante separar as tarefas diferentes em classes diferentes.
No neo é recomendada a utilização da seguinte organização:
- Controller - Recebe as requisições, faz o mínimo necessário e envia para a visão. Os controllers
devem passar qualquer tipo de calculo ou regra de negócio para os services. Representado no NEO
pela classe MultiActionController.
- Service - Efetua as regras de negócio. Se precisar de alguma informação persistente, pede aos DAOs.
Representado no NEO pela classe GenericService.
- DAO - Efetua pesquisas no banco de dados. Representado no NEO por GenericDAO.
Qualquer classe que extender MultiActionController, GenericService ou GenericDAO não precisa ser anotada com
@Bean. Uma explicação melhor sobre essas classes será feita nos capítulos seguintes.