modelo para a formatação dos artigos a serem utilizados

Propaganda
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
Framework utilizando reflexão e aspectos para persistência de objetos
em java
Antonio Carlos Rolloff (UNIPAR) [email protected]
Arthur Cattaneo Zavadski (UNIPAR) [email protected]
Maria Aparecida Denardi (UNIPAR) [email protected]
Resumo: A maioria das aplicações trabalha com dados e necessita armazenar os mesmos de
forma persistente, ou seja, que em uma nova execução possam recuperar e utilizar esses
dados, isto causa esforço e preocupação para os desenvolvedores, sendo uma complexidade
a mais no desenvolvimento. Com o objetivo de reduzir esta preocupação, este artigo propõe a
criação de uma camada que faça essa persistência. A proposta é o desenvolvimento de um
framework de persistência para Java, que através das técnicas de Aspectos e Reflexão,
consiga fazer o mapeamento objeto relacional, persistir e recuperar objetos em tabelas de
um banco de dados relacional.
Palavras-chave: Aspectos; Framework; Java; Persistência; Reflexão.
1. Introdução
A programação Orientada a Objetos vem sendo muito utilizada e junto com ela a
persistência de dados em bancos relacionais. Essa arquitetura é muito utilizada devido a
grande difusão e confiabilidade existente nos bancos de dados relacionais. Mas para
utilizarmos esta arquitetura é necessária a realização de um mapeamento Objeto-Relacional, o
qual vai associar objetos com tabelas. No desenvolvimento de software, realizar essa
persistência aumenta ainda mais a complexidade e se torna uma tarefa muito trabalhosa,
repetitiva e passível de erros para o desenvolvedor. Com isso surge a necessidade da criação
de um mecanismo que auxilie neste processo.
Este mecanismo é apresentado na forma de um Framework, ou seja, um conjunto de
classes que possuem como objetivo principal realizar a persistência de objetos sem que o
desenvolvedor necessite se preocupar com a forma como a persistência será realizada. Para
realizar esta persistência, o Framework utiliza recursos de reflexão Computacional para
conhecer e manipular o objeto e Aspectos para interceptar mudanças no Objeto.
2. Complexidade de Software
O desenvolvimento de software é uma tarefa complexa. Porém é possível dominar
essa complexidade, ou seja, aprender como vencer a mesma e encontrar soluções eficientes e
viáveis. A complexidade não esta apenas nos requisitos funcionais, mas também nos
requisitos não funcionais que geralmente são muito importantes e agregam complexidade ao
software (BOOCH 1998). A Orientação a Objetos veio para tentar reduzir essa complexidade,
porem existem alguns requisitos/preocupações que estão espalhadas em várias partes do
sistema, isso é conhecido como entrelaçamento de código. Para reduzir o entrelaçamento de
código e tentar reduzir a complexidade do desenvolvimento de software, surgiu a
Programação Orientada a Aspectos.
3. Persistência
Persistência é uma palavra utilizada para expressar o armazenamento de dados ou
informações de um sistema para uma futura recuperação e utilização. É o ato de persistir, ou
1
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
seja, preservar ou conservar a informação. Um exemplo amplamente utilizado é o
armazenamento de informações em bancos de dados relacionais. A persistência é uma das
complexidades de softwares citadas por (Booch 1998).
No desenvolvimento de software, a persistência pode ficar em uma camada separada,
com isso reduzindo a complexidade de desenvolvimento do software em si. Em uma
programação orientada a objetos, essa camada de persistência fica responsável pelo
recebimento de objetos e o seu armazenamento, de forma que possam ser futuramente
recuperados. A forma de armazenamento não importa para a aplicação, quem vai se preocupar
com isso é a camada de persistência. Essa camada de persistência pode ser desenvolvida na
forma de um framework, ou seja, um conjunto de classes que realizam determinadas tarefas,
permitindo a sua fácil reutilização.
4. Programação Orientada a Aspectos
A Programação Orientada a Aspectos surgiu com o objetivo de reduzir o
entrelaçamento de código, com isso reduzindo a complexidade do desenvolvimento de
software. Tem como principal premissa a separação de preocupações, ou seja, cada
preocupação do sistema é tratada separadamente (RESENDE 2005).
Como o próprio nome já sugere, a programação é separada em aspectos. Em
determinado momento é analisado e resolvido determinado aspecto, após esta resolução
passa-se ao aspecto seguinte, depois outro aspecto. Por exemplo, primeiro será cuidado
cuidamos do aspecto de persistência, depois cuida-se do aspecto de registro de log da
aplicação e assim por diante, até resolver todos os aspectos necessários no software em
questão (SOARES E BORBA 2004).
A Orientação a Aspectos não substitui a Orientação a Objetos, mas complementa seus
pontos fracos, resolvendo de forma mais simples alguns problemas que seriam muito
complexos de se resolver utilizando apenas a Orientação a Objetos. A principal finalidade é
reduzir o espalhamento de código, facilitando, por exemplo, mudanças de banco de dados ou
de mecanismos de distribuição, entre outros (RESENDE 2005).
Na Programação Orientada a Aspectos, é possível interceptar chamadas e introduzir
funcionalidades ou alterar a classe, além disso, na interceptação de chamadas é possível
acrescentar códigos, ou seja, acrescentar ou modificar funcionalidades da classe. Com isso
também é possível identificar chamadas e saber quando determinado método foi executado
(HILSDALE E KERSTEN 2004).
5. Reflexão Computacional
Java é uma linguagem reflexiva e oferece uma representação de sua própria estrutura,
com isso é possível coletar e utilizar informações sobre qualquer classe (ZAVADSKI 2003).
Existe uma Classe que representa as Classes, outra Classe que representa os Métodos, outra
que representa os Atributos, com isso é possível conhecer a estrutura de uma Classe e também
realizar chamadas a seus métodos e recuperar informações sem um prévio conhecimento do
método, com isso também é possível implementar uma solução que funcione mesmo com
novas classes que sejam criadas depois dela e que possuam diferentes funcionalidades.
(RESENDE 2005) Cita que também é possível utilizar reflexão em Aspectos.
2
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
6. Padrões de Projeto
Padrões de Projeto (Design Patterns) são modelos/soluções para diversas situações
(GAMMA 2000). Descreve soluções simples para problemas específicos no projeto de
software orientado a objetos, essas soluções já foram desenvolvidas e aperfeiçoadas ao longo
do tempo e são apresentadas na forma dos padrões, em uma forma sucinta e facilmente
aplicável para que novos desenvolvimentos possam aproveitar essas soluções, deixando o
código mais otimizado e reduzindo os esforços do desenvolvedor. Com isso pode-se utilizar
soluções já existentes sem ter que re-inventar a roda.
7. Framework para Persistência
7.1 Ferramentas Utilizadas
Para o desenvolvimento do framework, foi definido a utilização da linguagem de
programação Java JDK versão 1.5.0_04, a ferramenta de desenvolvimento Eclipse na versão
3.1.0, a ferramenta de modelagem UML Jude Comunity versão 1.6.2 e os bancos de dados
relacionais PostgreSql versão 8.0.0 e Firebird versão 1.5.1.4481. A ferramenta Eclipse vai ser
equipada com os seguintes plugins: Visual Editor versão 1.1.0, EMF versão 1.1.0, GEF
versão 3.1 e AspectJ versão 1.3.0. O Visual Editor, EMF e GEF são responsáveis pelo editor
gráfico de tela, embora o framework não seja composto por nenhuma tela, esse plugin foi
utilizado para criar a aplicação de teste do framework, pois essa possui tela gráfica. Já o
plugin AspectJ é um plugin do Eclipse para programar Orientado a Aspectos. A ferramenta de
UML Jude foi utilizada para a criação do diagrama de Classes do framework. Para testes
foram utilizados os bancos de dados PostgreSql e Firebird por serem bastante difundidos e
também de utilização livre, mas o objetivo do framework é poder trabalhar com outros
bancos, talvez sem nenhuma mudança por utilizar apenas comandos simples da linguagem
SQL, mas se existirem mudanças elas serão mínimas, desde que o banco de dados tenha
suporte a comunicação JDBC.
7.2 Funcionalidades Disponibilizadas
O framework de persistência irá disponibilizar as seguintes funcionalidades para o
usuário:
•
Receber um Objeto e Incluir as informações no banco de dados relacional.
•
Receber um Objeto e Alterar as informações no banco de dados relacional.
•
Receber um Objeto com pelo menos o atributo "ID" preenchido e Excluir a
informação no banco de dados relacional.
•
Recuperar informações do banco de dados relacional, converter em uma Coleção
(Collection) de Objetos e retornar essa Collection, com base na classe do Objeto,
num parâmetro e no nome da coluna a ser utilizada para ordenação.
Para atender essas funcionalidades e ainda incluir alguns recursos de otimização, o
framework de persistência será composto pelas seguintes Classes conforme Abaixo:
•
Entity
•
EntityManager
3
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
•
PersistenceManager
•
PersistenceException
•
SqlHelper
•
SingleConnection
•
Aspecto
7.3 Diagrama de Classes
FIGURA 1 – Diagrama de Classes do Framework
Fonte: Primária (2006).
7.4 Classe Entity
Todos os Objetos Persistentes devem estender essa classe, ou seja, ela é a super classe
dos Objetos Persistentes, nela é definida uma estrutura de controle para saber quais os
Atributos foram modificados, essa estrutura de controle é um atributo do tipo TreeMap
chamado "changeFields" que armazena os atributos que foram modificados, além deste, é
4
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
definido também um atributo "id" do tipo long que é comum para todas as classes
persistentes. Com isso as classes que herdam as funcionalidades desta não precisam declarar o
atributo "id". As outras classes do framework utilizam referências desta classe, para receber
instâncias dos objetos persistentes.
7.5 Classe EntityManager
A Classe EntityManager possui um método público e estático responsável pela
manipulação da estrutura de controle que a classe Entity possui dos atributos que foram
alterados. Essa classe é quem adiciona no atributo "changeFields", do tipo TreeMap, o
atributo que foi alterado. Esse método é chamado pela classe Aspecto quando o médoto de
alteração de um atributo é disparado. O método recebe como parâmetro, uma instância do
objeto, qual o atributo que foi alterado e qual o valor que esta sendo atribuído. Com essas
informações o método analisa e verifica se o valor que esta sendo atribuído é diferente do
valor existente, e se for diferente ele registra que o atributo em questão foi alterado. Isso faz
com que apenas os atributos que realmente foram modificados sejam adicionados na estrutura
de controle.
7.6 Classe SingleConnection
Classe SingleConnection implementa a interface Connection do pacote java.sql, e
possui um atributo desse tipo que executa todas as funcionalidades existentes na interface
Connection. Essa classe utiliza o padrão Singleton, que define a forma de se retornar uma
única instância, neste caso, a instância única é a instância da Connection (GAMMA 2000). A
conexão é realizada utilizando parâmetros cadastrados em um arquivo Properties. Neste
arquivo é definido o Driver de conexão ao banco, a Url de conexão, o usuário e a senha. A
instanciação da conexão é realizada quando ocorre a primeira chamada "getInstancia()" que é
um método estático, as informações do arquivo são carregadas no mesmo método e
imediatamente antes da instanciação ser realizada.
7.7 Classe Aspecto
Classe Aspecto é responsável pela interceptação das chamadas "set*" das classes que
herdam da classe Entity. Ao capturar uma chamada ela verifica se é para atributos
persistentes e aciona o método estático da classe EntityManager para registrar a alteração no
atributo. Ela passa então como parâmetro a instância do objeto, qual o atributo que foi
modificado e qual o valor que esta sendo atribuído. Todos os atributos que herdam de
“Object” e que pertençam à classes que herdam de Entity, são persistentes, ou seja, atributos
que são dos tipos de dados primitivos (“int”, “float”, “double” entre outros) não são
persistentes, mas os atributos dos tipos de dados que estendem de Objetos (Integer, Double,
Float entre outros). são persistentes, exceto os atributos que possuem o seu nome começando
com "_trans_". Isso serve para que o usuário possa ter atributos transientes em suas classes
persistentes, desde que comece o nome do atributo com "_trans_".
7.8 Classe SqlHelper
A Classe SqlHelper é responsável pela geração dos códigos SQL de manipulação. Ela
recebe um objeto que é extensão de Entity, e com esse objeto ela é capaz de retornar uma
String contendo o código SQL para Alterar, Incluir ou Excluir a informação no banco de
5
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
dados relacional. Essa classe utiliza o atributo "changeFields" da classe Entity para saber
quais os atributos foram alterados, e gera o código SQL respectivo apenas para os atributos
alterados no caso de Incluir e de Alterar, porém no caso do Excluir a classe utiliza apenas o
"id". O nome da tabela também é adquirido com base no objeto recebido. Para que isso
funcione o nome da Classe de objeto persistente deve ter o mesmo nome da tabela, pois é
através do método "getSimpleName()" da classe do objeto que é extraído o nome da tabela.
7.9 Classe PersistenceException
A Classe PersistenceException é proveniente da extenção da classe Exception, e é
utilizada pela classe PersistenceManager para disparar exceções. Esta exceção é disparada
sempre que a classe PersistenceManager encontrar algum problema. Com isso as classes, do
usuário, que utilizarem métodos da classe PersistenceManager que disparam exceções, terão
que tratar a exceção da forma que acharem mais conveniente.
7.10 Classe PersistenceManager
Classe PersistenceManager é a classe responsável pela persistência dos dados. É ela
quem fornece as funcionalidades do framework de persistência para o usuário,
disponibilizando métodos para inserir um objeto através do método “insert()”, alterar um
objeto através do método “update()”, excluir um objeto através do método “delete()”,
localizar uma coleção de objetos através do método “find()”, confirmar uma transação através
do método “commit()” e cancelar a transação através do método “rollBack()”. Essa classe é
capaz de utilizar as informações de uma tabela do banco de dados e através de chamadas
reflexivas enviar as informações para dentro do objeto correspondente. Ela utiliza o padrão
Singleton para retornar uma única instância, também segue o padrão Factory, pois é ela quem
gera as instâncias dos objetos persistentes que são extendidos de Entity.
7.11 Arquivo Properties
Foi criado um arquivo Properties para o armazenamento das informações de conexão
com o banco de dados. Basta o usuário alterar os parâmetros para que a persistência seja feita
da forma que o usuário deseja. Os parâmetros são:
•
driver – Para definir qual o driver de conexão com o banco de dados.
•
url – Para definir a url de conexão com o banco de dados(servidor, banco, etc.).
•
user – Para definir o nome do usuário de conexão.
•
password – Para definir a senha de conexão.
7.12 Restrições para Utilização do Framework
Para usufruir dessas funcionalidades, o usuário deve programar as suas classes de
Objetos Persistentes seguindo algumas restrições e padrões. As restrições são relacionadas
abaixo:
•
A classe persistente deve herdar da classe “Entity”, ou seja, deve conter em sua
declaração “extends Entity”;
•
O nome da classe persistente deve ser o mesmo nome utilizado na tabela no banco
6
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
de dados;
•
Os nomes dos atributos da classe persistente também devem ser exatamente iguais
aos nomes utilizados nos atributos na tabela do banco de dados;
•
Os atributos persistentes devem ser dos tipos de dados que estendem de “Object”,
por exemplo “Double”, “Float”, “Integer”, “Boolean”, “String”, entre outros. Mas
não podem ser de tipos primitivos como “double”, “float”, “int”, “boolean”;
•
Na classe persistente, para utilizar atributos globais que não sejam persistentes, o
nome do atributo deve começar com “_trans_” ou ser de um tipo de dado que não
estenda de “Object”, ou seja, tipos de dados primitivos, que é o critério utilizado
pelo framework para ignorar o atributo;
•
Ao recuperar um objeto persistente, o usuário deve fazer um cast para o tipo de
objeto desejado pois o framework vai retornar o objeto através de uma referência
de Entity, claro que essa referencia do tipo Entity vai conter um objeto do tipo que
foi solicitado ao método com o primeiro parâmetro “Classe”;
•
O framework desabilita a função de commit automático, com isso o usuário deve
executar a chamada ao método commit para confirmar a operação realizada;
•
As classes do framework devem ser copiadas e adicionadas ao projeto que
pretende utilizar o mesmo. As classes devem permanecer no pacote de origem,
pois o Aspecto utiliza o nome do pacote para não interceptar as chamadas do
próprio framework, o que geraria um loop infinito.
7.13 Dificuldades Encontradas
A idéia inicial seria a criação de um arquivo ".jar" que seria composto pelas classes do
framework, bastando o desenvolvedor da aplicação adicionar o JAR do framework no
ClassPath da sua aplicação e utilizar os seus recursos. Porém após testes foi constatado que
apenas adicionando o JAR no ClassPath não funcionava, as classes do framework podiam ser
utilizadas normalmente, mas o aspecto responsável pela interceptação das chamadas aos
objetos persistentes não era disparado, com isso a gravação não funcionava, apenas a leitura.
A forma encontrada para resolver o problema foi deixar as classes do framework abertas, fora
do JAR. Para a utilização do framework é necessário a copia das classes para dentro do
projeto que vai utilizar o mesmo, mesmo com essa copia, as classes do framework não vão se
misturar as classes do usuário, pois elas ficam em um pacote separado. Essa é uma das
restrições, ou melhor, procedimento necessário para possibilitar a utilização do framework.
Uma dificuldade encontrada foi capturar as chamadas “set” nos atributos das classes
persistentes, inicialmente pareceu fácil, porem estavam sendo capturadas todas as chamadas,
inclusive as chamadas do próprio framework, com isso gerava um loop infinito, pois ao
capturar uma chamada o framework executava uma chamada no objeto para registrar o
atributo que foi modificado. Após vários testes de diferentes formas, chegou-se ao modelo de
captura conforme a figura 2 abaixo. Com esse modelo, são capturadas todas as chamadas aos
métodos das classes que Herdam da classe “Entity” e que são iniciados com a palavra “set”
que recebem como parâmetro um “Object” ou uma classe que herde de “Object” e que não
pertencem ao pacote “org.acr.fraper” que é o pacote do framework.
FIGURA 2 – Pointcut de Interceptação de Alterações nos Atributos
Fonte: Primária (2006).
7
XIII SIMPEP - Bauru, SP, Brasil, 6 a 8 de Novembro de 2006
Outra dificuldade encontrada foi verificar se o valor que esta sendo atribuído era
realmente novo, para que fosse possível controlar as alterações verdadeiras, ou seja, as
alterações que realmente modificavam o valor do atributo. Para suprir essa necessidade foi
definido que todos os atributos fossem de tipos de dados que herdem de “Object”, pois com
isso é possível capturar o valor alterado utilizando uma referencia de “Object” e fazer a
comparação com o valor atual e verificar se ocorreu alguma mudança.
8. Conclusão
As dificuldades do desenvolvimento de aplicações em Java que fazem armazenamento
de dados, de forma persistente, podem ser amenizadas com a utilização de uma camada que
faça essa persistência.
Com a definição do seguinte pointcut: “pointcut ponto(): !(within(Entity)) && call(*
Entity+.set*(Object+) && !(within(org.acr.fraper.*));”, é possível capturar as chamadas
desejadas para saber quando esta sendo atribuído um valor a um atributo de um objeto
persistente. Ao capturar a chamada também é capturado o valor que esta sendo atribuído,
antes da atribuição, com isso é possível fazer uma comparação entre o valor antigo e o novo
valor para saber se o valor esta realmente sendo modificado ou é o mesmo valor que esta
sendo atribuído.
Com esta informação de quais atributos foram realmente modificados, é possível para
o framework fazer a persistência dos dados. Esta persistência é otimizada, o que faz melhorar
a performance de gravação dos dados ou de trafego na rede, pois apenas os atributos
realmente alterados serão gravados, com isso mesmo que uma tabela possua n campos, apenas
as informações que realmente foram alteradas vão ser enviadas ao banco de dados para a
gravação.
9. Referências Bibliográficas
BOOCH, Grady Object-oriented analysis and design with applications. 2nd ed. California: The
Benjamin/Cummings Publishing Company, 1998.
ZAVADSKI, Arthur C. Utilizando Reflexão Computacional no Desenvolvimento de Aplicações
Distribuídas. UFSC, 2003. Disponível em <http://www.ufsc.br>. Acesso em: 10 de nov. de 2005.
GAMMA, Erich Padrões de Projeto: soluções reutilizáveis de software orientado a objetos. Porto Alegre:
Bookman, 2000.
RESENDE, Antonio M. P. de Programação orientada a aspectos em Java. Rio de Janeiro: Brasport, 2005.
HILSDALE, Erik; KERSTEN, Mik Aspect-Oriented Programming with AspectJ. 2004. Disponível em:
<http://www.ime.usp.br/~kon/MAC5715/aulas/aspectj-tutorial.pdf>. Acesso em: 10 de nov. de 2005.
SOARES, Sergio; BORBA, Paulo Desenvolvimento de Software Orientados a Aspectos Utilizando RUP e
AspectJ.
2004.
Disponível
em:
<http://toritama.cin.ufpe.br/twiki/pub/SPG/
Talks/TutorialSBES2004_soares_borba.pdf>. Acesso em: 10 de nov. de 2005.
8
Download