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