Implementando uma anotação AOP personalizada do Spring

Implementando uma anotação AOP personalizada do Spring

1. Introdução

Neste artigo, implementaremos uma anotação AOP personalizada usando o suporte AOP no Spring.

Primeiro, daremos uma visão geral de alto nível do AOP, explicando o que é e suas vantagens. Em seguida, implementaremos nossa anotação passo a passo, construindo gradualmente uma compreensão mais profunda dos conceitos de AOP à medida que avançamos.

O resultado será uma melhor compreensão da AOP e a capacidade de criar nossas anotações personalizadas da Primavera no futuro.

2. O que é uma anotação AOP?

Para resumir rapidamente, AOP significa programação orientada a aspectos. Essencialmente,it is a way for adding behavior to existing code without modifying that code.

Para uma introdução detalhada ao AOP, existem artigos sobre AOPpointcutseadvice. Este artigo pressupõe que já temos um conhecimento básico.

O tipo de AOP que iremos implementar neste artigo é orientado a anotações. Podemos já estar familiarizados com isso se tivermos usado a anotação Spring@Transactional:

@Transactional
public void orderGoods(Order order) {
   // A series of database calls to be performed in a transaction
}

The key here is non-invasiveness. Usando metadados de anotação, nossa lógica de negócios principal não é poluída com nosso código de transação. Isso facilita raciocinar, refatorar e testar isoladamente.

Às vezes, as pessoas que desenvolvem aplicativos Spring podem ver isso comoSpring Magic ', sem pensar em muitos detalhes sobre como está funcionando. Na realidade, o que está acontecendo não é particularmente complicado. No entanto, depois de concluir as etapas neste artigo, seremos capazes de criar nossa própria anotação personalizada para entender e aproveitar o AOP.

3. Dependência do Maven

Primeiro, vamos adicionar nossoMaven dependencies.

Para este exemplo, usaremos Spring Boot, pois sua convenção sobre a abordagem de configuração nos permite começar a trabalhar o mais rápido possível:


    org.springframework.boot
    spring-boot-starter-parent
    1.5.2.RELEASE



    
        org.springframework.boot
        spring-boot-starter-aop
    

Observe que incluímos o iniciador AOP, que puxa as bibliotecas de que precisamos para começar a implementar aspectos.

4. Criação de nossa anotação personalizada

A anotação que vamos criar é aquela que será usada para registrar a quantidade de tempo que um método leva para executar. Vamos criar nossa anotação:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {

}

Embora seja uma implementação relativamente simples, é importante notar para que as duas meta-anotações são usadas.

A anotação@Target nos diz onde nossa anotação será aplicável. Aqui estamos usandoElementType.Method,, o que significa que só funcionará em métodos. Se tentássemos usar a anotação em qualquer outro lugar, nosso código não seria compilado. Esse comportamento faz sentido, pois nossa anotação será usada para o tempo de execução do método de log.

E@Retention apenas indica se a anotação estará disponível para a JVM no tempo de execução ou não. Por padrão, não é, portanto, o Spring AOP não poderá ver a anotação. É por isso que foi reconfigurado.

5. Criando Nosso Aspecto

Agora que temos nossa anotação, vamos criar nosso aspecto. Este é apenas o módulo que encapsulará nossa preocupação transversal, que é o nosso caso é o registro do tempo de execução do método. Tudo isso é uma classe, anotada com@Aspect:

@Aspect
@Component
public class ExampleAspect {

}

Também incluímos a anotação@Component, pois nossa classe também precisa ser um bean Spring para ser detectada. Essencialmente, esta é a classe em que implementaremos a lógica que queremos que nossa anotação personalizada injete.

6. Criando nosso ponto de vista e conselhos

Agora, vamos criar nosso ponto de corte e conselho. Este será um método anotado que vive em nosso aspecto:

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    return joinPoint.proceed();
}

Tecnicamente, isso não muda o comportamento de nada ainda, mas ainda há muita coisa acontecendo que precisa de análise.

Primeiro, anotamos nosso método com@Around.. Este é o nosso conselho, e em torno do conselho significa que estamos adicionando código extra antes e depois da execução do método. Existem outros tipos de conselhos, comobeforeeafter, mas eles serão deixados de fora do escopo deste artigo.

A seguir, nossa anotação@Around tem um argumento de corte de ponto. Nosso ponto de corte apenas diz, ‘Aplique este conselho a qualquer método que esteja anotado com@LogExecutionTime. Existem muitos outros tipos de pontos de atalho, mas eles serão novamente deixados de fora no caso de escopo.

O próprio métodologExecutionTime() é nosso conselho. Existe um único argumento,ProceedingJoinPoint. No nosso caso, este será um método de execução que foi anotado com@LogExecutionTime.

Finalmente, quando nosso método anotado acabar sendo chamado, o que acontecerá é que nosso conselho será chamado primeiro. Então, depende do nosso conselho decidir o que fazer a seguir. Em nosso caso, nosso conselho nada mais é do que chamarproceed(),, que é apenas chamar o método anotado original.

7. Registrando nosso tempo de execução

Agora que temos nosso esqueleto no lugar, tudo o que precisamos fazer é adicionar uma lógica extra ao nosso conselho. Isso será o que registra o tempo de execução, além de chamar o método original. Vamos adicionar este comportamento extra ao nosso conselho:

@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();

    Object proceed = joinPoint.proceed();

    long executionTime = System.currentTimeMillis() - start;

    System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
    return proceed;
}

Novamente, não fizemos nada que seja particularmente complicado aqui. Acabamos de registrar a hora atual, executar o método e imprimir a quantidade de tempo que demorou no console. Também estamos registrando a assinatura do método, que é fornecida para usar a instânciajoinpoint. Também poderíamos obter acesso a outros bits de informação, se quiséssemos, como argumentos de método.

Agora, vamos tentar anotar um método com@LogExecutionTime,e executá-lo para ver o que acontece. Observe que este deve ser um Spring Bean para funcionar corretamente:

@LogExecutionTime
public void serve() throws InterruptedException {
    Thread.sleep(2000);
}

Após a execução, devemos ver o seguinte registrado no console:

void org.example.Service.serve() executed in 2030ms

8. Conclusão

Neste artigo, aproveitamos o Spring Boot AOP para criar nossa anotação personalizada, que podemos aplicar aos beans Spring para injetar um comportamento extra a eles em tempo de execução.

O código-fonte do nosso aplicativo está disponível emover on GitHub; este é um projeto Maven que deve ser executado como está.