AspectJ - Programação Orientada a Aspectos em Java Sérgio Soares Centro de Informática Universidade Federal de Pernambuco Programação Orientada a Objetos Lida com conceitos mais intuitivos Permite ganhos • Reuso • Manutenção • Adaptação Padrões de projetos • Auxiliam a POO Exemplo: Sistema Disque Saúde Um sistema de informação • Registra e encaminha queixas para o sistema de saúde • Exibe informações sobre unidades de saúde e suas especialidades Requisitos não-funcionais • Distribuído • Acesso concorrente • Extensível —Armazenamento de dados —Tecnologia de distribuição Exemplo: Sistema Disque Saúde Implementado em Java • Servlets implementam a GUI • Utiliza vários padrões de projetos para contemplar requisitos não-funcionais Como implementar a distribuição no sistema? Disque Saúde local Disque Saúde distribuído com RMI public class Complaint implements java.io.Serializable { public class HealthWatcherFacade implements IFacade { private String description; public class ServletUpdateComplaintData extends HttpServlet { public void update(Complaint complaint) private Person complainer; ... private IFacade facade; throws TransactionException, RepositoryException, public Complaint(String description, Person complainer, ...) { public void init(ServletConfig config) throws ServletException { ObjectNotFoundException, ObjectNotValidException { try { ... ... facade = (IFacade) java.rmi.Naming.lookup("//HealthWatcher"); } } } public static void main(String[] args) { public String getDescription() { catch (java.rmi.RemoteException rmiEx) {...} try { return this.description; catch (java.rmi.NotBoundException rmiEx) {...} HealthWatcherFacade facade = HealthWatcherFacade.getInstance(); } catch (java.net.MalformedURLException rmiEx) {...} System.out.println("Creating RMI server..."); public Person getComplainer() { } UnicastRemoteObject.exportObject(facade); return this.complainer; java.rmi.Naming.rebind("/HealthWatcher"); } System.out.println("Server created and ready."); public void setDescription(String desc) { this.description = desc; } public void setComplainer(Person complainer) { throws ServletException, IOException { catch (RemoteException rmiEx) {... } ... catch (MalformedURLException rmiEx) { ...} facade.update(complaint); catch(Exception ex) {... } this.complainer = complainer; } ... public void doPost(HttpServletRequest request, HttpServletResponse response) } ... } } ... } } } public class Person implements java.io.Serializable { public interface IFacade extends java.rmi.Remote { private String nome; ... public Person(String nome, …) { public void updateComplaint complaint) this.nome = nome; … throws TransactionException, RepositoryException, } ObjectNotFoundException, ObjectNotValidException, public String getNome() { RemoteException; return nome; }… ... } } Código RMI é vermelho… Implementação OO: problemas! Tangled code (código entrelaçado) • código de distribuição misturado com código de negócio e de GUI Spread code (código espalhado) • código de distribuição em várias classes • distribuição é um crosscutting concern Difícil de manter e reusar • mudanças no protocolo de distribuirão (RMI, CORBA, EJB ) são invasivas AOP — Aspect-oriented programming Melhora a modularidade de crosscutting concerns • distribuição, gerenciamento de dados, controle de concorrência, tratamento de exceções, logging, debugging, … Auxilia separation of concerns • Aumenta extensibilidade e reuso Disque Saúde com AOP public class Complaint { private String description; private Person complainer; ... public class ServletUpdateComplaintData extends HttpServlet { public Complaint(String description, Person complainer, ...) { aspect DistributionAspect { private HealthWatcherFacade facade; declare parents: HealthWatcherFacade implements IFacade; public void init(ServletConfig config) throws ServletException { declare parents: Complaint || Person implements java.io.Serializable; ... try { } public static void HealthWatcherFacade.main(String[] args) { facade = HealthWatcherFacade.getInstance(); public String getDescription() { return this.description; } try { } HealthWatcherFacade facade = HealthWatcherFacade.getInstance(); catch (Exception ex) {...} System.out.println("Creating RMI server..."); } public Person getComplainer() { return this.complainer; UnicastRemoteObject.exportObject(facade); public void doPost(HttpServletRequest request, HttpServletResponse response) } java.rmi.Naming.rebind("/HealthWatcher"); System.out.println("Server created and ready."); throws ServletException, IOException { public void setDescription(String desc) { ... this.description = desc; } } catch (RemoteException rmiEx) {...} catch (MalformedURLException rmiEx) {...} } ... catch(Exception ex) {...} } } public void setComplainer(Person complainer) { this.complainer = complainer; } } public class Person { private String nome; ... public Person(String nome, ...) { this.matricula = matricula; ... } public String getNome() { return nome; private IFacade remoteFacade; Sistema local pointcut facadeMethodsExecution(): within(HttpServlet+) && execution(* HealthWatcherFacade.*(..)) && this(HealthWatcherFacade); before(): facadeMethodsExecution() { prepareFacade();} private synchronized void prepareFacade() { Aspectos de Distribuição para RMI if (healthWatcher == null) { try { remoteFacade = (IFacade) java.rmi.Naming.lookup("//HealthWatcher"); } catch (java.rmi.RemoteException rmiEx) {...} catch (java.rmi.NotBoundException rmiEx) {...} catch (java.net.MalformedURLException rmiEx) {...} } } ... void around(Complaint complaint) throws TransactionException, RepositoryException } ObjectNotFoundException,ObjectNotValidException: public interface IFacade extends java.rmi.Remote { facadeRemoteExecutions() && args(complaint) && public class HealthWatcherFacade { } RemoteException; ... ... } remoteFacade.update(complaint); } catch (RemoteException rmiEx) {...} ObjectNotFoundException, ObjectNotValidException, ObjectNotFoundException, ObjectNotValidException { } try { throws TransactionException, RepositoryException, throws TransactionException, RepositoryException, } call(void update(Complaint)) { public void updateComplaint complaint) public void update(Complaint complaint) } Disque Saúde com AOP Implementação com AOP Aumento em modularidade, reuso e extensibidade • Mais unidades de código • Mudanças no sistema local podem causar impacto nos aspectos de distribuição Separation of concerns • Relação entre os aspectos e o resto do sistema nem sempre é clara Normalmente menos linhas de código Passo 1: Identificando concerns Fonte: JavaWorld Concerns do Disque Saúde Tratamento de Exceções Interface com o Usuário Armazenamento de dados Distribuição Regras do Negócio Controle de Concorrência Passo 2: Implementar o sistema, separando os concerns Alguns concerns são bem modelados como objetos (núcleo do sistema) • GUI • Regras de negócio Outros (crosscutting concerns) como aspectos • Distribuição, Controle de Concorrência, Tratamento de Exceções, Armazenamento de Dados Passo 3: Recompor o sistema Fonte: JavaWorld Weaving é usado para … Compor o “núcleo” do sistema com os aspectos Sistema original chamadas locais entre A e B A Processo de recomposição Sistema distribuído chamadas remotas entre A e B Aspectos de distribuição B Weaver A B Protocolo de distribução Composição nos join points a method is called and returns or throws object A a method is called and returns or throws dispatch object B a method executes and returns or throws Comportamento pode ser alterado nos join points… dispatch a method executes and returns or throws Fonte: AspectJ Tutorial aspectj.org Pointcuts especificam join points Identificam join points de um sistema • chamadas e execuções de métodos (e construtores) • acessos a atributos • tratamento de exceções • inicialização estática e dinâmica • expõe o contexto nos join points —argumentos de métodos, objetos alvo, atributos Composição de join points • &&, || e ! AspectJ: identificando chamadas de métodos da fachada (servidor) identifica código dentro da classe ... nome do pointcut pointcut facadeMethodsCall(): within(HttpServlet+) && call(* IFacade+.*(..)); identifica chamadas de … qualquer método com quaisquer argumentos Advice especifica comportamento extra nos join points Define código adicional que deve ser executado… • before • after —after returning —after throwing • ou around join points AspectJ: antes (before) de chamar métodos da fachada private IFacade remoteFacade; before(): facadeMethodsCall() { getRemoteInstance(); } synchronized void getRemoteInstance() {... remoteFacade = (IFacade) java.rmi.Naming.lookup(...); ...} AspectJ: transformando chamadas locais em remotas void around(Complaint c) throws Ex1,…: facadeMethodsCall() && args(c) && call(void update(Complaint)) { try { remoteFacade.update(c); obtendo e utilizando argumento de método em um join point } catch (RemoteException rmiEx) {...} } Além de dynamic crosscutting com advice… AspectJ suporta crosscutting static • alterar relação de subtipo • adicionar membros a classes introductions AspectJ: static crosscutting declare parents: HealthWatcherFacade implements IFacade; declare parents: Complaint || Person implements java.io.Serializable; public static void HealthWatcherFacade.main(String[] args){ try {... java.rmi.Naming.rebind("/HW"); } catch } ... Adicionando o método main na classe fachada Alterando a hierarquia de tipos Mais construtores de AspectJ target(<nome do tipo>) • Identifica join points onde o objeto alvo é uma instância de <nome do tipo> this(<nome do tipo>) • Identifica join points cujo objeto em execução é uma instância de <nome do tipo> withincode(<assinatura de método>) • Identifica join points cujo código em execução pertence ao corpo do método ou construtor especificado por <assinatura de método> Mais construtores de AspectJ cflow(<pointcut>) • Identifica join points que estejam no fluxo de execução identificado por <pointcut> get(<assinatura>) / set(<assinatura>) • Leitura/atribuição a atributos declare soft: <nome do tipo>:<pointcut>; • A exceção <nome do tipo> será encapsulada em uma exceção não checada em tempo de compilação (runtime) em qualquer join point definido por <pointcut> Distribution AspectJ aspect in public aspect DistributionAspect { declare parents: ... private IFacade remoteFacade; public static void HealthWatcherFacade.main(String[] as)... pointcut facadeMethodsCall(): ... before(): facadeMethodsCall() ... private synchronized void getRemoteInstance() ... void around(Complaint complaint) ... } Aspectos de desenvolvimento: debugging simples com AspectJ public aspect DatabaseDebugging { pointcut queryExecution(String sql): call(* Statement.*(String)) && args(sql); before(String sql): queryExecution(sql) { System.out.println(sql); } } AspectJ: pontos positivos Modularidade, reuso, e extensibilidade de software Obliviousness Suporte a desenvolvimento com IDEs Produtividade Permite implementação e testes progressivos AspectJ: pontos negativos Novo paradigma • relação entre classes e aspectos deve ser minimizada • obliviousness Projeto da linguagem • tratamento de exceções • conflitos entre aspectos Requer suporte de ferramentas Ambiente de desenvolvimento Static weaving AOP ou um bom projeto OO? Padrão de projeto Adapter Com adaptadores… Escrevemos mais código A ligação entre o adaptador e o objeto adaptado • é explicita e invasiva • não altera o comportamento de chamadas internas para o objeto adaptado • não tem acesso ao objeto fonte (source) • pode ser modificado dinamicamente Referências Gregor Kiczales et. al. Aspect-Oriented Programming. European Conference on Object-Oriented Programming, ECOOP'97 JavaWorld, “I want my AOP”. http://www.aosd.net/ http://www.eclipse.org/aspectj http://groups.yahoo.com/group/asoc-br/ Exercícios – PARTE 1 Considere a classe Conta a seguir package contas; public class Conta { private String numero; private double saldo; public Conta(String numero, double saldo)... public Conta(String numero) ... public String getNumero() ... public double getSaldo() ... public void creditar(double valor) ... public void debitar(double valor) throws SaldoInsuficienteException ... } Exercício 1 Defina o aspecto Teste com um pointcut para identificar todas as chamadas ao método creditar de objetos do tipo Conta. Agora, no mesmo aspecto, defina um advice para imprimir a mensagem "Vou creditar" antes das chamadas ao método creditar de objetos do tipo Conta. Exercício 2 Crie outro aspecto (Teste2) e defina um pointcut para identificar todas as execuções do método creditar de um objeto do tipo Conta expondo o objeto do qual o método vai ser executado. Defina um advice para imprimir o saldo da conta após a execução do método creditar. O que deve acontecer caso ambos os aspectos (Teste e Teste2) forem compostos com o sistema? Exercícios - PARTE 2 Agora considere a seguinte classe CadastroContas package contas; public class CadastroContas { private RepositorioContas contas; ... public void transferir(String nDe, String nPara, double valor) throws ContaNaoEncontradaException, SaldoInsuficienteException { Conta de = contas.procurar(nDe); Conta para = contas.procurar(nPara); de.debitar(valor); para.creditar(valor); } } Exercício 3 Mais uma vez usando o projeto de aplicação bancária defina o aspecto TrocaOrdemArgumentos que troca a ordem dos números das contas na chamada do método transferir da classe CadastroContas. Use o advice around. Exercício 4 Agora defina o aspecto TransacaoTransferencia. Este aspecto deve imprimir uma mensagem após a execução com sucesso do método creditar e do método debitar de uma conta, mas somente se os mesmos forem chamados devido a execução de uma transferência. Use o designator cflow.