Spring Framework é desenvolvido com base em dois conceitos principais – Dependency Injection e Aspect Oriented Programming (Spring AOP).
Spring AOP
Já vimos como funciona a Injeção de Dependência do Spring, hoje vamos examinar os conceitos principais da Programação Orientada a Aspectos e como podemos implementá-la usando o Spring Framework.
Visão Geral do Spring AOP
A maioria das aplicações empresariais possui algumas preocupações comuns que são aplicáveis a diferentes tipos de objetos e módulos. Algumas das preocupações comuns são o registro de atividades, o gerenciamento de transações, a validação de dados, etc. Na Programação Orientada a Objetos, a modularidade da aplicação é alcançada por meio de classes, enquanto na Programação Orientada a Aspectos, a modularidade da aplicação é alcançada por Aspectos, que são configurados para atravessar diferentes classes. O Spring AOP elimina a dependência direta de tarefas transversais das classes, o que não é possível alcançar por meio do modelo normal de programação orientada a objetos. Por exemplo, podemos ter uma classe separada para registro de atividades, mas novamente as classes funcionais terão que chamar esses métodos para alcançar o registro em toda a aplicação.
Conceitos Principais da Programação Orientada a Aspectos
Antes de nos aprofundarmos na implementação do Spring AOP, devemos entender os conceitos principais da POA.
- Aspecto: Um aspecto é uma classe que implementa preocupações de aplicação empresarial que atravessam várias classes, como o gerenciamento de transações. Os aspectos podem ser uma classe normal configurada por meio da configuração XML do Spring ou podemos usar a integração Spring AspectJ para definir uma classe como Aspecto usando a anotação
@Aspect
. - Ponto de Junção: Um ponto de junção é um ponto específico na aplicação, como a execução de um método, o tratamento de exceções, a alteração de valores de variáveis de objeto, etc. No Spring AOP, um ponto de junção é sempre a execução de um método.
- Conselho: Conselhos são ações tomadas para um ponto de junção específico. Em termos de programação, são métodos que são executados quando um determinado ponto de junção com um pointcut correspondente é alcançado na aplicação. Você pode pensar nos Conselhos como interceptadores do Struts2 ou Filtros do Servlet.
- Pointcut: Pointcut são expressões que são correspondidas com pontos de junção para determinar se o conselho precisa ser executado ou não. Pointcut usa diferentes tipos de expressões que são correspondidas com os pontos de junção, e o framework Spring usa a linguagem de expressão pointcut do AspectJ.
- Objeto Alvo: Eles são o objeto no qual os conselhos são aplicados. O Spring AOP é implementado usando proxies em tempo de execução, então este objeto é sempre um objeto proxificado. Isso significa que uma subclasse é criada em tempo de execução, onde o método de destino é sobrescrito e os conselhos são incluídos com base em sua configuração.
- Proxy AOP: A implementação do Spring AOP utiliza proxy dinâmico JDK para criar classes Proxy com classes de destino e invocações de conselhos, chamadas de classes de proxy AOP. Também podemos usar o proxy CGLIB adicionando-o como dependência no projeto Spring AOP.
- Entrelaçamento: É o processo de vincular aspectos a outros objetos para criar objetos de proxy aconselhados. Isso pode ser feito em tempo de compilação, tempo de carregamento ou em tempo de execução. O Spring AOP realiza o entrelaçamento em tempo de execução.
Tipos de Conselhos AOP
Com base na estratégia de execução do conselho, eles são dos seguintes tipos.
- Conselho Antes: Esses conselhos são executados antes da execução dos métodos do ponto de junção. Podemos usar a anotação
@Before
para marcar um tipo de conselho como conselho antes. - Conselho Depois (finalmente): Um conselho que é executado após o método do ponto de junção terminar a execução, seja normalmente ou lançando uma exceção. Podemos criar um conselho depois usando a anotação
@After
. - Conselho Após o Retorno: Às vezes, queremos que os métodos de aconselhamento sejam executados apenas se o método do ponto de junção for executado normalmente. Podemos usar a anotação
@AfterReturning
para marcar um método como conselho após o retorno. - Depois de Lançar Conselhos: Este conselho é executado apenas quando o método do ponto de junção lança uma exceção, podemos usá-lo para reverter a transação de forma declarativa. Usamos a anotação
@AfterThrowing
para este tipo de conselho. - Conselho de Ao Redor: Este é o conselho mais importante e poderoso. Este conselho envolve o método do ponto de junção e também podemos escolher se devemos ou não executar o método do ponto de junção. Podemos escrever código de conselho que é executado antes e depois da execução do método do ponto de junção. É responsabilidade do conselho de ao redor invocar o método do ponto de junção e retornar valores se o método estiver retornando algo. Usamos a anotação
@Around
para criar métodos de conselho de ao redor.
Os pontos mencionados acima podem parecer confusos, mas quando olharmos para a implementação do Spring AOP, as coisas ficarão mais claras. Vamos começar criando um projeto Spring simples com implementações de AOP. O Spring fornece suporte para usar anotações AspectJ para criar aspectos e vamos usar isso para simplicidade. Todas as anotações AOP mencionadas acima estão definidas no pacote org.aspectj.lang.annotation
. Spring Tool Suite fornece informações úteis sobre os aspectos, então eu sugeriria que você o usasse. Se você não está familiarizado com o STS, recomendaria que você desse uma olhada no Tutorial Spring MVC, onde expliquei como usá-lo.
Exemplo de Spring AOP
Crie um novo projeto Maven Spring simples para que todas as bibliotecas do Spring Core sejam incluídas nos arquivos pom.xml e não precisemos incluí-las explicitamente. Nosso projeto final parecerá com a imagem abaixo, iremos analisar detalhadamente os componentes principais do Spring e as implementações de Aspecto.
Dependências do Spring AOP AspectJ
O framework Spring fornece suporte para AOP por padrão, mas como estamos usando anotações AspectJ para configurar aspectos e conselhos, precisaremos incluí-los no arquivo pom.xml.
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId>
<artifactId>SpringAOPExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring -->
<spring-framework.version>4.0.2.RELEASE</spring-framework.version>
<!-- Logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
<!-- Test -->
<junit.version>4.11</junit.version>
<!-- AspectJ -->
<aspectj.version>1.7.4</aspectj.version>
</properties>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- AspectJ dependencies -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</project>
Observe que adicionei as dependências aspectjrt
e aspectjtools
(versão 1.7.4) no projeto. Além disso, atualizei a versão do Spring framework para a mais recente até o momento, ou seja, 4.0.2.RELEASE.
Classe Modelo
Vamos criar um simples bean Java que usaremos em nosso exemplo com alguns métodos adicionais. Código Employee.java:
package com.journaldev.spring.model;
import com.journaldev.spring.aspect.Loggable;
public class Employee {
private String name;
public String getName() {
return name;
}
@Loggable
public void setName(String nm) {
this.name=nm;
}
public void throwException(){
throw new RuntimeException("Dummy Exception");
}
}
Você percebeu que o método setName() está anotado com a anotação Loggable
. É uma anotação Java personalizada definida por nós no projeto. Vamos analisar o seu uso mais tarde.
Classe de Serviço
Vamos criar uma classe de serviço para trabalhar com o bean Employee. Código EmployeeService.java:
package com.journaldev.spring.service;
import com.journaldev.spring.model.Employee;
public class EmployeeService {
private Employee employee;
public Employee getEmployee(){
return this.employee;
}
public void setEmployee(Employee e){
this.employee=e;
}
}
I could have used Spring annotations to configure it as a Spring Component, but we will use XML based configuration in this project. EmployeeService class is very standard and just provides us an access point for Employee beans.
Configuração do Bean Spring com AOP
Se estiver usando STS, você tem a opção de criar um “Arquivo de Configuração de Bean Spring” e escolher o namespace do esquema AOP, mas se estiver usando outro IDE, pode adicioná-lo simplesmente no arquivo de configuração do bean Spring. Meu arquivo de configuração de bean do projeto parece assim. spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="https://www.springframework.org/schema/aop"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.0.xsd
https://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- Enable AspectJ style of Spring AOP -->
<aop:aspectj-autoproxy />
<!-- Configure Employee Bean and initialize it -->
<bean name="employee" class="com.journaldev.spring.model.Employee">
<property name="name" value="Dummy Name"></property>
</bean>
<!-- Configure EmployeeService bean -->
<bean name="employeeService" class="com.journaldev.spring.service.EmployeeService">
<property name="employee" ref="employee"></property>
</bean>
<!-- Configure Aspect Beans, without this Aspects advices wont execute -->
<bean name="employeeAspect" class="com.journaldev.spring.aspect.EmployeeAspect" />
<bean name="employeeAspectPointcut" class="com.journaldev.spring.aspect.EmployeeAspectPointcut" />
<bean name="employeeAspectJoinPoint" class="com.journaldev.spring.aspect.EmployeeAspectJoinPoint" />
<bean name="employeeAfterAspect" class="com.journaldev.spring.aspect.EmployeeAfterAspect" />
<bean name="employeeAroundAspect" class="com.journaldev.spring.aspect.EmployeeAroundAspect" />
<bean name="employeeAnnotationAspect" class="com.journaldev.spring.aspect.EmployeeAnnotationAspect" />
</beans>
Para usar o Spring AOP em beans do Spring, precisamos fazer o seguinte:
- Declarar o namespace AOP como xmlns:aop=“https://www.springframework.org/schema/aop”
- Adicionar o elemento aop:aspectj-autoproxy para habilitar o suporte ao Spring AspectJ com proxy automático em tempo de execução
- Configurar classes de Aspecto como outros beans do Spring
Você pode ver que eu tenho muitos aspectos definidos no arquivo de configuração de bean do Spring, é hora de olhar para eles um por um.
Exemplo de Aspecto Before do Spring AOP
Código EmployeeAspect.java:
package com.journaldev.spring.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class EmployeeAspect {
@Before("execution(public String getName())")
public void getNameAdvice(){
System.out.println("Executing Advice on getName()");
}
@Before("execution(* com.journaldev.spring.service.*.get*())")
public void getAllAdvice(){
System.out.println("Service method getter called");
}
}
Pontos importantes na classe de aspecto acima são:
- Aspecto classes precisam ter a anotação
@Aspect
. - A anotação @Before é usada para criar conselhos Before
- O parâmetro de string passado na anotação
@Before
é a expressão Pointcut - O conselho getNameAdvice() será executado para qualquer método de Spring Bean com a assinatura
public String getName()
. Este é um ponto muito importante a lembrar, se criarmos um bean Employee usando o operador new, os conselhos não serão aplicados. Somente quando usarmos o ApplicationContext para obter o bean, os conselhos serão aplicados. - Podemos usar asterisco (*) como curinga em expressões Pointcut, o getAllAdvice() será aplicado para todas as classes no pacote
com.journaldev.spring.service
cujo nome começa comget
e não aceita argumentos.
Vamos ver o conselho em ação em uma classe de teste depois de termos examinado todos os diferentes tipos de conselhos.
Métodos de Pontuação do Spring AOP e Reutilização
Às vezes, temos que usar a mesma expressão de ponto de corte em vários lugares, podemos criar um método vazio com a anotação @Pointcut
e então usá-lo como uma expressão nos conselhos. Código EmployeeAspectPointcut.java:
package com.journaldev.spring.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class EmployeeAspectPointcut {
@Before("getNamePointcut()")
public void loggingAdvice(){
System.out.println("Executing loggingAdvice on getName()");
}
@Before("getNamePointcut()")
public void secondAdvice(){
System.out.println("Executing secondAdvice on getName()");
}
@Pointcut("execution(public String getName())")
public void getNamePointcut(){}
@Before("allMethodsPointcut()")
public void allServiceMethodsAdvice(){
System.out.println("Before executing service method");
}
//Ponto de corte para executar em todos os métodos de classes em um pacote
@Pointcut("within(com.journaldev.spring.service.*)")
public void allMethodsPointcut(){}
}
O exemplo acima é muito claro, em vez de expressão, estamos usando o nome do método no argumento da anotação do conselho.
Ponto de Junção e Argumentos de Conselho do Spring AOP
Podemos usar JoinPoint como parâmetro nos métodos de conselho e, usando-o, obter a assinatura do método ou o objeto alvo. Podemos usar a expressão args()
no ponto de corte para ser aplicada a qualquer método que corresponda ao padrão de argumento. Se usarmos isso, então precisamos usar o mesmo nome no método de conselho de onde o tipo de argumento é determinado. Também podemos usar objetos genéricos nos argumentos de conselho. Código EmployeeAspectJoinPoint.java:
package com.journaldev.spring.aspect;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class EmployeeAspectJoinPoint {
@Before("execution(public void com.journaldev.spring.model..set*(*))")
public void loggingAdvice(JoinPoint joinPoint){
System.out.println("Before running loggingAdvice on method="+joinPoint.toString());
System.out.println("Agruments Passed=" + Arrays.toString(joinPoint.getArgs()));
}
//Argumentos de conselho, serão aplicados aos métodos de bean com um único argumento de String
@Before("args(name)")
public void logStringArguments(String name){
System.out.println("String argument passed="+name);
}
}
Exemplo de Conselho After do Spring AOP
Vamos dar uma olhada em uma classe de aspecto simples com um exemplo de conselhos After, After Throwing e After Returning. Código EmployeeAfterAspect.java:
package com.journaldev.spring.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class EmployeeAfterAspect {
@After("args(name)")
public void logStringArguments(String name){
System.out.println("Running After Advice. String argument passed="+name);
}
@AfterThrowing("within(com.journaldev.spring.model.Employee)")
public void logExceptions(JoinPoint joinPoint){
System.out.println("Exception thrown in Employee Method="+joinPoint.toString());
}
@AfterReturning(pointcut="execution(* getName())", returning="returnString")
public void getNameReturningAdvice(String returnString){
System.out.println("getNameReturningAdvice executed. Returned String="+returnString);
}
}
Podemos usar within
na expressão do ponto de corte para aplicar o conselho a todos os métodos na classe. Podemos usar o conselho @AfterReturning para obter o objeto retornado pelo método aconselhado. Temos o método throwException() no bean Employee para mostrar o uso do conselho After Throwing.
Exemplo de Aspecto Around do Spring AOP
Como explicado anteriormente, podemos usar o aspecto Around para cortar a execução do método antes e depois. Podemos usá-lo para controlar se o método aconselhado será executado ou não. Também podemos inspecionar o valor retornado e alterá-lo. Este é o conselho mais poderoso e precisa ser aplicado adequadamente. Código EmployeeAroundAspect.java:
package com.journaldev.spring.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class EmployeeAroundAspect {
@Around("execution(* com.journaldev.spring.model.Employee.getName())")
public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("Before invoking getName() method");
Object value = null;
try {
value = proceedingJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("After invoking getName() method. Return value="+value);
return value;
}
}
Ao redor do conselho são sempre necessários ter o ProceedingJoinPoint como argumento e devemos usar o método proceed() para invocar o método do objeto alvo aconselhado. Se o método aconselhado estiver retornando algo, é responsabilidade do conselho devolvê-lo ao programa chamador. Para métodos void, o método de aconselhamento pode retornar null. Como o conselho ao redor corta em torno do método aconselhado, podemos controlar a entrada e saída do método, bem como o seu comportamento de execução.
Conselho Spring com Ponto de Corte de Anotação Personalizada
Se você observar todas as expressões de ponto de corte de conselhos acima, há chances de que elas sejam aplicadas a alguns outros beans onde não é pretendido. Por exemplo, alguém pode definir um novo spring bean com o método getName(), e o conselho começará a ser aplicado a isso, mesmo que não fosse a intenção. É por isso que devemos manter o escopo da expressão de ponto de corte o mais estreito possível. Uma abordagem alternativa é criar uma anotação personalizada e anotar os métodos onde queremos que o conselho seja aplicado. Este é o propósito de ter o método Employee \texttt{setName()} anotado com a anotação \texttt{@Loggable}. A anotação \texttt{@Transactional} do Spring Framework é um ótimo exemplo dessa abordagem para \texttt{Gerenciamento de Transações Spring}. Código Loggable.java:
package com.journaldev.spring.aspect;
public @interface Loggable {
}
Código EmployeeAnnotationAspect.java:
package com.journaldev.spring.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class EmployeeAnnotationAspect {
@Before("@annotation(com.journaldev.spring.aspect.Loggable)")
public void myAdvice(){
System.out.println("Executing myAdvice!!");
}
}
O método myAdvice() aconselhará apenas o método setName(). Esta é uma abordagem muito segura e sempre que quisermos aplicar o conselho a qualquer método, tudo o que precisamos fazer é anotá-lo com a anotação Loggable.
Configuração XML do Spring AOP
I always prefer annotation but we also have the option to configure aspects in the spring configuration file. For example, let’s say we have a class as below. EmployeeXMLConfigAspect.java code:
package com.journaldev.spring.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class EmployeeXMLConfigAspect {
public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("EmployeeXMLConfigAspect:: Before invoking getName() method");
Object value = null;
try {
value = proceedingJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("EmployeeXMLConfigAspect:: After invoking getName() method. Return value="+value);
return value;
}
}
Podemos configurá-lo incluindo a seguinte configuração no arquivo de configuração do Spring Bean.
<bean name="employeeXMLConfigAspect" class="com.journaldev.spring.aspect.EmployeeXMLConfigAspect" />
<!-- Spring AOP XML Configuration -->
<aop:config>
<aop:aspect ref="employeeXMLConfigAspect" id="employeeXMLConfigAspectID" order="1">
<aop:pointcut expression="execution(* com.journaldev.spring.model.Employee.getName())" id="getNamePointcut"/>
<aop:around method="employeeAroundAdvice" pointcut-ref="getNamePointcut" arg-names="proceedingJoinPoint"/>
</aop:aspect>
</aop:config>
Os elementos de configuração AOP XML têm seu propósito claro a partir de seus nomes, então não entrarei em muitos detalhes sobre isso.
Exemplo de AOP Spring
Vamos ter um programa simples em Spring e ver como todos esses aspectos atravessam os métodos do bean. Código SpringMain.java:
package com.journaldev.spring.main;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.journaldev.spring.service.EmployeeService;
public class SpringMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
EmployeeService employeeService = ctx.getBean("employeeService", EmployeeService.class);
System.out.println(employeeService.getEmployee().getName());
employeeService.getEmployee().setName("Pankaj");
employeeService.getEmployee().throwException();
ctx.close();
}
}
Agora, ao executar o programa acima, obtemos a seguinte saída.
Mar 20, 2014 8:50:09 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Thu Mar 20 20:50:09 PDT 2014]; root of context hierarchy
Mar 20, 2014 8:50:09 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
Service method getter called
Before executing service method
EmployeeXMLConfigAspect:: Before invoking getName() method
Executing Advice on getName()
Executing loggingAdvice on getName()
Executing secondAdvice on getName()
Before invoking getName() method
After invoking getName() method. Return value=Dummy Name
getNameReturningAdvice executed. Returned String=Dummy Name
EmployeeXMLConfigAspect:: After invoking getName() method. Return value=Dummy Name
Dummy Name
Service method getter called
Before executing service method
String argument passed=Pankaj
Before running loggingAdvice on method=execution(void com.journaldev.spring.model.Employee.setName(String))
Agruments Passed=[Pankaj]
Executing myAdvice!!
Running After Advice. String argument passed=Pankaj
Service method getter called
Before executing service method
Exception thrown in Employee Method=execution(void com.journaldev.spring.model.Employee.throwException())
Exception in thread "main" java.lang.RuntimeException: Dummy Exception
at com.journaldev.spring.model.Employee.throwException(Employee.java:19)
at com.journaldev.spring.model.Employee$$FastClassBySpringCGLIB$$da2dc051.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:711)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
at com.journaldev.spring.model.Employee$$EnhancerBySpringCGLIB$$3f881964.throwException(<generated>)
at com.journaldev.spring.main.SpringMain.main(SpringMain.java:17)
Você pode ver que os conselhos estão sendo executados um por um com base em suas configurações de ponto de corte. Você deve configurá-los um por um para evitar confusões. Isso é tudo para o Tutorial de Exemplo de AOP Spring, espero que você tenha aprendido o básico do AOP com o Spring e possa aprender mais com exemplos. Baixe o projeto de exemplo no link abaixo e brinque com ele.