Поиск по сайту:

Учебное пособие по Spring AOP — Aspect, Advice, Pointcut, JoinPoint, Annotations, XML Configuration


Spring Framework разработан на основе двух основных концепций — внедрения зависимостей и аспектно-ориентированного программирования (Spring AOP).

Весенний АОП

Мы уже видели, как работает Spring Dependency Injection, сегодня мы рассмотрим основные концепции аспектно-ориентированного программирования и то, как мы можем реализовать его с помощью Spring Framework.

Обзор Spring АОП

Большинство корпоративных приложений имеют некоторые общие сквозные проблемы, применимые к различным типам объектов и модулей. Некоторыми из общих сквозных проблем являются ведение журнала, управление транзакциями, проверка данных и т. Д. В объектно-ориентированном программировании модульность приложения достигается с помощью классов, тогда как в аспектно-ориентированном программировании модульность приложения достигается с помощью аспектов, и они настроены для пересечения различных классов. Spring AOP устраняет прямую зависимость сквозных задач от классов, чего мы не можем достичь с помощью обычной модели объектно-ориентированного программирования. Например, у нас может быть отдельный класс для ведения журнала, но опять же функциональные классы должны будут вызывать эти методы для обеспечения ведения журнала во всем приложении.

Ключевые понятия аспектно-ориентированного программирования

Прежде чем мы углубимся в реализацию Spring AOP, мы должны понять основные концепции AOP.

  1. Аспект. Аспект – это класс, реализующий функции корпоративного приложения, охватывающие несколько классов, например управление транзакциями. Аспекты могут быть обычным классом, настроенным с помощью конфигурации XML Spring, или мы можем использовать интеграцию Spring AspectJ, чтобы определить класс как Aspect с помощью аннотации @Aspect.
  2. Точка присоединения. Точка присоединения – это конкретная точка в приложении, например, выполнение метода, обработка исключений, изменение значений объектных переменных и т. д. В Spring AOP точка присоединения – это всегда выполнение метода.
  3. Совет. Советы – это действия, выполняемые для определенной точки соединения. С точки зрения программирования, это методы, которые выполняются, когда в приложении достигается определенная точка соединения с соответствующим pointcut. Советы можно рассматривать как фильтры сервлетов.
  4. Pointcut: Pointcut — это выражения, которые сопоставляются с точками соединения, чтобы определить, нужно ли выполнять совет или нет. Pointcut использует различные виды выражений, которые сопоставляются с точками соединения, а платформа Spring использует язык выражений pointcut AspectJ.
  5. Целевой объект: это объект, к которому применяются рекомендации. Spring AOP реализован с использованием прокси-серверов времени выполнения, поэтому этот объект всегда является прокси-объектом. Это означает, что во время выполнения создается подкласс, в котором целевой метод переопределяется, а рекомендации включаются в зависимости от их конфигурации.
  6. Прокси-сервер AOP. В реализации Spring AOP используется динамический прокси-сервер JDK для создания классов прокси с целевыми классами и вызовами рекомендаций, которые называются классами прокси-серверов AOP. Мы также можем использовать прокси-сервер CGLIB, добавив его в качестве зависимости в проект Spring AOP.
  7. Сплетение: это процесс связывания аспектов с другими объектами для создания рекомендованных прокси-объектов. Это можно сделать во время компиляции, во время загрузки или во время выполнения. Spring AOP выполняет переплетение во время выполнения.

Типы рекомендаций АОП

В зависимости от стратегии исполнения советов они бывают следующих видов.

  1. Перед советом. Эти советы запускаются перед выполнением методов точки соединения. Мы можем использовать аннотацию @Before, чтобы пометить тип совета как «до совета».
  2. После (наконец) совета: совет, который выполняется после завершения выполнения метода точки соединения, либо в обычном режиме, либо путем создания исключения. Мы можем создать совет после, используя аннотацию @After.
  3. После возврата совета. Иногда нам нужно, чтобы методы совета выполнялись только в том случае, если метод точки соединения выполняется нормально. Мы можем использовать аннотацию @AfterReturning, чтобы пометить метод как после возврата совета.
  4. After Throwing Advice: этот совет выполняется только тогда, когда метод точки соединения генерирует исключение, мы можем использовать его для декларативного отката транзакции. Мы используем аннотацию @AfterThrowing для советов такого типа.
  5. Around Advice: это самый важный и действенный совет. Этот совет касается метода точки соединения, и мы также можем выбрать, выполнять ли метод точки соединения или нет. Мы можем написать код совета, который будет выполняться до и после выполнения метода точки соединения. Совет вокруг должен вызывать метод точки соединения и возвращать значения, если метод что-то возвращает. Мы используем аннотацию @Around для создания методов консультации вокруг.

Упомянутые выше моменты могут показаться запутанными, но когда мы рассмотрим реализацию Spring AOP, все станет более ясно. Давайте начнем создавать простой проект Spring с реализациями АОП. Spring поддерживает использование аннотаций AspectJ для создания аспектов, и мы будем использовать это для простоты. Все вышеуказанные аннотации АОП определены в пакете org.aspectj.lang.annotation. Spring Tool Suite предоставляет полезную информацию об аспектах, поэтому я бы посоветовал вам использовать ее. Если вы не знакомы с STS, я бы порекомендовал вам взглянуть на Spring MVC Tutorial, где я объяснил, как его использовать.

Весенний пример АОП

Зависимости Spring AOP AspectJ

Платформа Spring обеспечивает поддержку AOP по умолчанию, но, поскольку мы используем аннотации AspectJ для настройки аспектов и рекомендаций, нам нужно будет включить их в файл 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>

Обратите внимание, что я добавил в проект зависимости aspectjrt и aspectjtools (версия 1.7.4). Также я обновил версию среды Spring, чтобы она была последней на сегодняшний день, то есть 4.0.2.RELEASE.

Класс модели

Давайте создадим простой Java-бин, который мы будем использовать в нашем примере с некоторыми дополнительными методами. Код сотрудника.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");
	}	
}

Вы заметили, что метод setName() снабжен аннотацией Loggable. Это пользовательская аннотация Java, определенная нами в проекте. Мы рассмотрим его использование позже.

Класс обслуживания

Давайте создадим сервисный класс для работы с bean-компонентом Employee. Код 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;
	}
}

Я мог бы использовать аннотации Spring, чтобы настроить его как компонент Spring, но в этом проекте мы будем использовать конфигурацию на основе XML. Класс EmployeeService очень стандартный и просто предоставляет нам точку доступа для бинов Employee.

Конфигурация Spring Bean с AOP

Если вы используете STS, у вас есть возможность создать «Файл конфигурации Spring Bean» и выбрать пространство имен схемы AOP, но если вы используете какую-либо другую IDE, вы можете просто добавить его в файл конфигурации Spring Bean. Файл конфигурации моего проекта bean выглядит так, как показано ниже.

<?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>

Для использования Spring AOP в компонентах Spring нам необходимо сделать следующее:

  1. Объявите пространство имен AOP, например xmlns:aop=\https://www.springframework.org/schema/aop
  2. Добавьте элемент aop:aspectj-autoproxy, чтобы включить поддержку Spring AspectJ с автоматическим прокси во время выполнения.
  3. Настроить классы Aspect как другие компоненты Spring

Вы можете видеть, что у меня есть много аспектов, определенных в файле конфигурации bean-компонента Spring, пришло время изучить их один за другим.

Spring AOP перед примером аспекта

Код 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");
	}
}

Важными моментами в вышеуказанном классе аспектов являются:

  • Классы аспектов должны иметь аннотацию @Aspect.
  • Аннотация @Before используется для создания рекомендации «До»
  • Строковый параметр, передаваемый в аннотации @Before, представляет собой выражение Pointcut
  • Совет
  • getNameAdvice() будет выполняться для любого метода Spring Bean с сигнатурой public String getName(). Это очень важный момент, который следует помнить: если мы создадим компонент Employee с помощью нового оператора, советы не будут применяться. Советы будут применяться только тогда, когда мы будем использовать ApplicationContext для получения компонента.
  • Можно использовать звездочку (*) в качестве подстановочного знака в выражениях Pointcut, getAllAdvice() будет применяться ко всем классам в пакете com.journaldev.spring.service имя которого начинается с get и не принимает никаких аргументов.

Мы рассмотрим советы в действии на тестовом занятии после того, как изучим все различные типы советов.

Методы Spring AOP Pointcut и повторное использование

Иногда нам приходится использовать одно и то же выражение Pointcut в нескольких местах, мы можем создать пустой метод с аннотацией @Pointcut и затем использовать его как выражение в советах. Код 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");
	}
	
	//Pointcut to execute on all the methods of classes in a package
	@Pointcut("within(com.journaldev.spring.service.*)")
	public void allMethodsPointcut(){}
	
}

Приведенный выше пример очень ясен, вместо выражения мы используем имя метода в аргументе аннотации совета.

Spring AOP JoinPoint и аргументы Advice

Мы можем использовать JoinPoint в качестве параметра в методах-советах и с его помощью получить сигнатуру метода или целевой объект. Мы можем использовать выражение args() в pointcut для применения к любому методу, который соответствует шаблону аргумента. Если мы используем это, то нам нужно использовать то же имя в методе совета, из которого определяется тип аргумента. Мы можем использовать общие объекты также в аргументах совета. Код 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()));

	}
	
	//Advice arguments, will be applied to bean methods with single String argument
	@Before("args(name)")
	public void logStringArguments(String name){
		System.out.println("String argument passed="+name);
	}
}

Пример Spring AOP после консультации

Давайте рассмотрим простой класс аспектов на примере советов After, After Throwing и After Return. Код 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);
	}
	
}

Мы можем использовать within в выражении pointcut, чтобы применить совет ко всем методам в классе. Мы можем использовать совет @AfterReturning, чтобы получить объект, возвращаемый рекомендуемым методом. У нас есть метод throwException() в компоненте Employee, чтобы продемонстрировать использование совета After Throwing.

Пример Spring AOP вокруг аспекта

Как объяснялось ранее, мы можем использовать аспект вокруг, чтобы сократить выполнение метода до и после. Мы можем использовать его, чтобы контролировать, будет ли выполняться рекомендуемый метод или нет. Мы также можем проверить возвращаемое значение и изменить его. Это самый действенный совет, и его нужно правильно применять. Код 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;
	}
}

Совет вокруг всегда должен иметь ProceedingJoinPoint в качестве аргумента, и мы должны использовать его метод continue() для вызова метода, рекомендованного целевому объекту. Если рекомендуемый метод что-то возвращает, ответственность за возврат этого вызывающей программе лежит на совете. Для пустых методов метод совета может возвращать ноль. Поскольку рекомендации вокруг обходятся с рекомендуемым методом, мы можем контролировать ввод и вывод метода, а также его поведение при выполнении.

Spring Advice с пользовательской аннотацией Pointcut

Если вы посмотрите на все вышеприведенные выражения pointcut с рекомендациями, есть вероятность, что они будут применены к некоторым другим компонентам, где они не предназначены. Например, кто-то может определить новый компонент Spring с помощью метода getName(), и к нему начнут применяться советы, даже если они не были предназначены. Вот почему мы должны максимально сузить область действия выражения pointcut. Альтернативный подход — создать пользовательскую аннотацию и аннотировать методы, к которым мы хотим применить рекомендации. Это цель метода Employee setName(), аннотированного с помощью Spring Transaction Management. Loggable.java код:

package com.journaldev.spring.aspect;

public @interface Loggable {

}

Код 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!!");
	}
}

Метод myAdvice() будет советовать только метод setName(). Это очень безопасный подход, и всякий раз, когда мы хотим применить совет к какому-либо методу, все, что нам нужно, — это аннотировать его аннотацией Loggable.

XML-конфигурация Spring AOP

Я всегда предпочитаю аннотацию, но у нас также есть возможность настроить аспекты в файле конфигурации spring. Например, предположим, что у нас есть класс, как показано ниже. Код EmployeeXMLConfigAspect.java:

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;
	}
}

Мы можем настроить его, включив следующую конфигурацию в файл конфигурации 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>

Назначение элементов конфигурации AOP xml понятно из их названия, поэтому я не буду вдаваться в подробности.

Весенний пример АОП

Давайте создадим простую программу на Spring и посмотрим, как все эти аспекты пересекаются с методами компонента. Код 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();
	}
}

Теперь, когда мы выполняем вышеуказанную программу, мы получаем следующий вывод.

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)

Вы можете видеть, что советы выполняются один за другим в зависимости от их конфигураций pointcut. Вы должны настроить их один за другим, чтобы избежать путаницы. Это все, что касается учебника Spring AOP Example Tutorial, я надеюсь, что вы изучили основы AOP с помощью Spring и сможете узнать больше на примерах. Загрузите образец проекта по ссылке ниже и поэкспериментируйте с ним.

Скачать проект Spring AOP