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 como‘Spring 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á.