JavaRush /Blogue Java /Random-PT /O que é AOP? Noções básicas de programação orientada a as...

O que é AOP? Noções básicas de programação orientada a aspectos

Publicado no grupo Random-PT
Olá, pessoal! Sem compreender os conceitos básicos, é bastante difícil aprofundar-se nas estruturas e abordagens para a construção de funcionalidades. Então hoje falaremos sobre um desses conceitos - AOP, ou programação orientada a aspectos . O que é AOP?  Fundamentos da Programação Orientada a Aspectos - 1Este não é um tópico fácil e nem sempre é usado diretamente, mas muitas estruturas e tecnologias o utilizam nos bastidores. E, claro, às vezes, durante as entrevistas, você pode ser solicitado a dizer em termos gerais que tipo de animal é esse e onde pode ser usado. Então, vamos dar uma olhada nos conceitos básicos e alguns exemplos simples de AOP em Java . O que é AOP?  Fundamentos da Programação Orientada a Aspectos - 2Assim, AOP - programação orientada a aspectos - é um paradigma que visa aumentar a modularidade de várias partes de uma aplicação, separando preocupações transversais. Para fazer isso, um comportamento adicional é adicionado ao código existente, sem alterar o código original. Em outras palavras, parece que adicionamos funcionalidades adicionais aos métodos e classes sem fazer alterações no código modificado. Por que isso é necessário? Mais cedo ou mais tarde, chegaremos à conclusão de que a abordagem usual orientada a objetos nem sempre pode resolver certos problemas com eficácia. Nesse momento, o AOP vem em socorro e nos fornece ferramentas adicionais para construir o aplicativo. E ferramentas adicionais significam maior flexibilidade no desenvolvimento, graças às quais há mais opções para resolver um problema específico.

Aplicação de POA

A programação orientada a aspectos é projetada para resolver problemas transversais, que podem ser qualquer código repetido muitas vezes de maneiras diferentes, que não pode ser completamente estruturado em um módulo separado. Assim, com AOP podemos deixar isso fora do código principal e defini-lo verticalmente. Um exemplo é a aplicação de uma política de segurança em uma aplicação. Normalmente, a segurança abrange muitos elementos de um aplicativo. Além disso, a política de segurança da aplicação deve ser aplicada igualmente a todas as partes novas e existentes da aplicação. Ao mesmo tempo, a política de segurança utilizada pode evoluir. É aqui que o uso do AOP pode ser útil . Outro exemplo é o logging . Há diversas vantagens em usar uma abordagem AOP para registro em log em comparação com a inserção manual do registro em log:
  1. O código de registro é fácil de implementar e remover: você só precisa adicionar ou remover algumas configurações de algum aspecto.
  2. Todo o código-fonte para registro é armazenado em um só lugar e não há necessidade de localizar manualmente todos os locais de uso.
  3. O código destinado ao registro pode ser adicionado em qualquer lugar, sejam métodos e classes já escritos ou novas funcionalidades. Isso reduz o número de erros do desenvolvedor.
    Além disso, ao remover um aspecto de uma configuração de design, você pode ter certeza absoluta de que todo o código de rastreamento foi removido e nada está faltando.
  4. Aspectos são códigos independentes que podem ser reutilizados e melhorados continuamente.
O que é AOP?  Fundamentos da Programação Orientada a Aspectos - 3AOP também é usado para tratamento de exceções, armazenamento em cache e remoção de algumas funcionalidades para torná-lo reutilizável.

Conceitos básicos de AOP

Para avançar na análise do tema, vamos primeiro conhecer os principais conceitos da AOP. Conselho é lógica adicional, código, que é chamado a partir do ponto de conexão. Os conselhos podem ser realizados antes, depois ou no lugar do ponto de conexão (mais sobre eles abaixo). Possíveis tipos de conselhos :
  1. Antes (Antes) - conselhos deste tipo são lançados antes da execução dos métodos alvo - pontos de conexão. Ao usar aspectos como classes, usamos a anotação @Before para marcar o tipo de conselho como anterior. Ao usar aspectos como arquivos .aj , este será o método before() .
  2. After (Depois) - conselho que é executado após a conclusão da execução dos métodos - pontos de conexão, tanto em casos normais quanto quando uma exceção é lançada.
    Ao utilizar aspectos como classes, podemos utilizar a anotação @After para indicar que esta é uma dica que vem depois.
    Ao usar aspectos como arquivos .aj , este será o método after() .
  3. Após Retornar - essas dicas são executadas somente se o método alvo funcionar normalmente, sem erros.
    Quando os aspectos são representados como classes, podemos usar a anotação @AfterReturning para marcar o conselho como executado após a conclusão bem-sucedida.
    Ao usar aspectos como arquivos .aj, este será o retorno do método after() (Object obj) .
  4. After Throwing - este tipo de conselho é destinado aos casos em que um método, ou seja, um ponto de conexão, lança uma exceção. Podemos usar esse conselho para lidar com a execução com falha (por exemplo, reverter a transação inteira ou registrar com o nível de rastreamento necessário).
    Para classes de aspecto, a anotação @AfterThrowing é usada para indicar que este conselho é usado após uma exceção ser lançada.
    Ao usar aspectos na forma de arquivos .aj , este será o método - after() throw (Exception e) .
  5. Around é talvez um dos conselhos mais importantes que envolve um método, ou seja, um ponto de conexão, com o qual podemos, por exemplo, escolher se executaremos ou não um determinado método de ponto de conexão.
    Você pode escrever um código de aconselhamento que seja executado antes e depois da execução do método de ponto de junção.
    As responsabilidades do conselho incluem chamar o método join point e retornar valores se o método retornar algo. Ou seja, nesta dica você pode simplesmente imitar o funcionamento do método alvo sem chamá-lo, e retornar algo de sua preferência como resultado.
    Para aspectos na forma de classes, usamos a anotação @Around para criar dicas que envolvem o ponto de conexão. Ao usar aspectos como arquivos .aj , este será o método around() .
Join point - um ponto em um programa em execução (chamando um método, criando um objeto, acessando uma variável) onde o conselho deve ser aplicado. Ou seja, trata-se de uma espécie de expressão regular, com a ajuda da qual se encontram locais para introdução de código (locais para aplicação de dicas). Um pointcut é um conjunto de pontos de conexão . O corte determina se um determinado ponto de conexão se ajusta a uma determinada ponta. Aspect é um módulo ou classe que implementa funcionalidade ponta a ponta. Um aspecto modifica o comportamento do restante do código aplicando conselhos em pontos de junção definidos por alguma fatia . Em outras palavras, é uma combinação de dicas e pontos de conexão. Introdução - alterar a estrutura de uma classe e/ou alterar a hierarquia de herança para adicionar funcionalidade de aspecto ao código estrangeiro. Alvo é o objeto ao qual o conselho será aplicado. Weaving é o processo de vincular aspectos a outros objetos para criar os objetos proxy recomendados. Isso pode ser feito em tempo de compilação, carregamento ou execução. Existem três tipos de tecelagem:
  • Tecelagem em tempo de compilação - Se você tiver o código-fonte de um aspecto e o código no qual usa aspectos, poderá compilar o código-fonte e o aspecto diretamente usando o compilador AspectJ;
  • tecelagem pós-compilação (tecelagem binária) - se você não pode ou não deseja usar transformações de código-fonte para tecer aspectos em seu código, você pode pegar classes ou jars já compilados e injetar aspectos;
  • a tecelagem em tempo de carregamento é simplesmente uma tecelagem binária adiada até que o carregador de classes carregue o arquivo de classe e defina a classe para a JVM.
    Para suportar isso, são necessários um ou mais "carregadores de classes weave". Eles são fornecidos explicitamente pelo tempo de execução ou ativados pelo "agente de tecelagem".
AspectJ é uma implementação específica de paradigmas AOP que implementa a capacidade de resolver problemas transversais. A documentação pode ser encontrada aqui .

Exemplos em Java

A seguir, para entender melhor o AOP, veremos pequenos exemplos do nível Hello World. O que é AOP?  Fundamentos da Programação Orientada a Aspectos - 4Deixe-me observar imediatamente que usaremos tecelagem em tempo de compilação em nossos exemplos . Primeiro precisamos adicionar a seguinte dependência ao nosso pom.xml :
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.5</version>
</dependency>
Como regra, um compilador Ajs especial é usado para usar aspectos . O IntelliJ IDEA não o possui por padrão, portanto, ao escolhê-lo como compilador de aplicativos, você precisa especificar o caminho para a distribuição AspectJ . Você pode ler mais sobre o método de escolha de Ajs como compilador nesta página. Este foi o primeiro método, e o segundo (que usei) foi adicionar o seguinte plugin ao pom.xml :
<build>
  <plugins>
     <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
           <complianceLevel>1.8</complianceLevel>
           <source>1.8</source>
           <target>1.8</target>
           <showWeaveInfo>true</showWeaveInfo>
           <verbose>true</verbose>
           <Xlint>ignore</Xlint>
           <encoding>UTF-8</encoding>
        </configuration>
        <executions>
           <execution>
              <goals>
                 <goal>compile</goal>
                 <goal>test-compile</goal>
              </goals>
           </execution>
        </executions>
     </plugin>
  </plugins>
</build>
Depois disso, é aconselhável reimportar do Maven e executar mvn clean compile . Agora vamos passar aos exemplos.

Exemplo nº 1

Vamos criar uma classe Main . Nele teremos um ponto de lançamento e um método que imprime os nomes passados ​​para ele no console:
public class Main {

  public static void main(String[] args) {
  printName("Толя");
  printName("Вова");
  printName("Sasha");
  }

  public static void printName(String name) {
     System.out.println(name);
  }
}
Nada complicado: passaram o nome e exibiram no console. Se executarmos agora, o console exibirá:
Tolya Vova Sasha
Bem, é hora de aproveitar o poder do AOP. Agora precisamos criar um aspecto de arquivo . Eles vêm em dois tipos: o primeiro é um arquivo com extensão .aj , o segundo é uma classe regular que implementa recursos AOP usando anotações. Vejamos primeiro um arquivo com extensão .aj :
public aspect GreetingAspect {

  pointcut greeting() : execution(* Main.printName(..));

  before() : greeting() {
     System.out.print("Привет ");
  }
}
Este arquivo é um pouco semelhante a uma classe. Vamos descobrir o que está acontecendo aqui: pointcut - um corte ou conjunto de pontos de conexão; saudação() — o nome desta fatia; : execução - ao executar * - todos, chame - Main.printName(..) - este método. Em seguida vem o conselho específico - before() - que é executado antes do método alvo ser chamado,: saudação() - a fatia à qual este conselho reage, e abaixo vemos o corpo do próprio método, que está escrito no Java linguagem que entendemos. Quando executamos main com este aspecto presente, obteremos a seguinte saída no console:
Olá Tolya, Olá Vova, Olá Sasha
Podemos ver que cada chamada ao método printName foi modificada por um aspecto. Agora vamos dar uma olhada em como ficará o aspecto, mas como uma classe Java com anotações:
@Aspect
public class GreetingAspect{

  @Pointcut("execution(* Main.printName(String))")
  public void greeting() {
  }

  @Before("greeting()")
  public void beforeAdvice() {
     System.out.print("Привет ");
  }
}
Depois do arquivo de aspecto .aj , tudo fica mais óbvio:
  • @Aspect denota que a classe dada é um aspecto;
  • @Pointcut("execution(* Main.printName(String))") é um ponto de corte que é acionado em todas as chamadas para Main.printName com um argumento de entrada do tipo String ;
  • @Before("greeting()") - conselho que é aplicado antes de chamar o código descrito no ponto de corte Greeting() .
Executar main com este aspecto não alterará a saída do console:
Olá Tolya, Olá Vova, Olá Sasha

Exemplo nº 2

Digamos que temos algum método que realiza algumas operações para clientes e chamamos esse método de main :
public class Main {

  public static void main(String[] args) {
  makeSomeOperation("Толя");
  }

  public static void makeSomeOperation(String clientName) {
     System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  }
}
Usando a anotação @Around , vamos fazer algo como uma “pseudo-transação”:
@Aspect
public class TransactionAspect{

  @Pointcut("execution(* Main.makeSomeOperation(String))")
  public void executeOperation() {
  }

  @Around(value = "executeOperation()")
  public void beforeAdvice(ProceedingJoinPoint joinPoint) {
     System.out.println("Открытие транзакции...");
     try {
        joinPoint.proceed();
        System.out.println("Закрытие транзакции....");
     }
     catch (Throwable throwable) {
        System.out.println("Операция не удалась, откат транзакции...");
     }
  }
  }
Usando o método prosseguir do objeto ProceedingJoinPoint , chamamos o método do wrapper para determinar seu lugar no quadro e, consequentemente, o código no método acima joinPoint.proceed(); - isto é Antes , que está abaixo de - Depois . Se executarmos main, entraremos no console:
Abrindo uma transação... Realizando algumas operações para o cliente - Tolya Fechando uma transação....
Se adicionarmos um lançamento de exceção ao nosso método (de repente a operação falha):
public static void makeSomeOperation(String clientName)throws Exception {
  System.out.println("Выполнение некоторых операций для клиента - " + clientName);
  throw new Exception();
}
Então obteremos a saída no console:
Abrindo uma transação... Realizando algumas operações para o cliente - Tolya A operação falhou, a transação foi revertida...
Acabou sendo um pseudoprocessamento de falha.

Exemplo nº 3

No próximo exemplo, vamos fazer algo como fazer login no console. Primeiro, vamos dar uma olhada em Main , onde acontece nossa pseudo lógica de negócios:
public class Main {
  private String value;

  public static void main(String[] args) throws Exception {
     Main main = new Main();
     main.setValue("<некоторое meaning>");
     String valueForCheck = main.getValue();
     main.checkValue(valueForCheck);
  }

  public void setValue(String value) {
     this.value = value;
  }

  public String getValue() {
     return this.value;
  }

  public void checkValue(String value) throws Exception {
     if (value.length() > 10) {
        throw new Exception();
     }
  }
}
Em main , usando setValue, definiremos o valor da variável interna - value , depois usando getValue pegaremos esse valor e em checkValue verificaremos se esse valor tem mais de 10 caracteres. Se sim, uma exceção será lançada. Agora vejamos o aspecto com o qual registraremos o funcionamento dos métodos:
@Aspect
public class LogAspect {

  @Pointcut("execution(* *(..))")
  public void methodExecuting() {
  }

  @AfterReturning(value = "methodExecuting()", returning = "returningValue")
  public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
     if (returningValue != null) {
        System.out.printf("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName(),
              returningValue);
     }
     else {
        System.out.printf("Успешно выполнен метод - %s, класса- %s\n",
              joinPoint.getSignature().getName(),
              joinPoint.getSourceLocation().getWithinType().getName());
     }
  }

  @AfterThrowing(value = "methodExecuting()", throwing = "exception")
  public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
     System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n",
           joinPoint.getSignature().getName(),
           joinPoint.getSourceLocation().getWithinType().getName(),
           exception);
  }
}
O que está acontecendo aqui? @Pointcut("execution(* *(..))") - conectará a todas as chamadas para todos os métodos; @AfterReturning(value = "methodExecuting()", return = "returningValue") - conselho que será executado após a conclusão bem-sucedida do método de destino. Temos dois casos aqui:
  1. Quando um método tem um valor de retorno if (returningValue != null) {
  2. Quando não há valor de retorno else {
@AfterThrowing(value = "methodExecuting()", throw = "exception") - aviso que será acionado em caso de erro, ou seja, quando uma exceção for lançada do método. E consequentemente, executando main , obteremos uma espécie de log no console:
O método - setValue, da classe - Main foi executado com sucesso. O método - getValue, da classe - Main, foi executado com sucesso, com o resultado da execução - <algum valor> O método - checkValue, da classe - Main, foi encerrado de forma anormal com uma exceção - método java.lang.Exception - main, class-Main, travou com uma exceção - java.lang.Exception
Bom, como não tratamos a exceção, também obteremos seu stacktrace: O que é AOP?  Fundamentos da Programação Orientada a Aspectos - 5Você pode ler sobre exceções e seu tratamento nestes artigos: Exceções em Java e Exceções e seu tratamento . Isso é tudo para mim hoje. Hoje conhecemos AOP e você pode ver que essa fera não é tão assustadora quanto é retratada. Adeus a todos!O que é AOP?  Fundamentos da Programação Orientada a Aspectos - 6
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION