Отладка Spring Boot с аспектно-ориентированным программированием (АОП) для повышения модульности
26 апреля 2023 г.Аспектно-ориентированное программирование (АОП) – это парадигма программирования, направленная на повышение модульности за счет разделения сквозных задач. Проще говоря, это поможет вам сохранить ваш код чистым, модульным и простым в обслуживании. Мы можем использовать АОП для более эффективной и беспрепятственной отладки приложений Spring Boot. В этом посте мы рассмотрим, как использовать АОП для эффективной отладки приложения Spring Boot.
https://youtu.be/6kxpkcEqssw?embedable=true
Концепции АОП
Прежде чем погрузиться в отладку с помощью АОП, важно понять основные концепции. АОП позволяет нам писать код, который выполняется до или после метода. Он включает следующие общие термины:
* Аспект. Аспект представляет сквозные проблемы или функциональные возможности, которые необходимо применять во всем приложении. * Точка присоединения. Точка присоединения – это конкретная точка в потоке выполнения приложения, к которой можно применить аспект. * Совет. Действие, предпринятое аспектом в определенной точке соединения, называется советом. Существуют разные типы советов, например, до, после, вокруг и после броска. * Pointcut: Pointcut — это набор точек соединения, к которым следует применить аспект.
Я пропущу обсуждение плетения, так как с этим связано много нюансов, и я хочу сосредоточиться на аспектах отладки. Я не хочу превращать это в учебник по АОП. Однако есть один общий аспект АОП, который я хочу обсудить.
Хотя АОП предлагает значительные преимущества с точки зрения модульности, простоты сопровождения и возможностей отладки, важно помнить о возможных последствиях для производительности.
Использование АОП может привести к некоторым накладным расходам, главным образом из-за создания и выполнения прокси-объектов, которые перехватывают вызовы методов и применяют указанные рекомендации. Влияние на производительность может варьироваться в зависимости от количества аспектов, сложности выражений pointcut и типа используемого совета. Например, консультация по вопросам, как правило, обходится дороже с точки зрения эффективности по сравнению с консультациями до и после.
Чтобы свести к минимуму влияние АОП на производительность, рассмотрите следующие рекомендации:
- Будьте избирательны. Применяйте АОП к критически важным частям вашего приложения, где он обеспечивает наибольшую ценность. Избегайте использования аспектов для тривиальных или нечастых операций.
- Оптимизация выражений pointcut. Убедитесь, что ваши выражения pointcut максимально точны. Это может помочь свести к минимуму количество ненужных перехватов и сократить накладные расходы.
- Ограничить выполнение рекомендации. Выберите тип рекомендации, подходящий для вашего варианта использования. Например, по возможности используйте совет до или после совета, а не вокруг совета.
- Использовать условные аспекты. Если некоторые аспекты требуются только для целей отладки или разработки, используйте условные аспекты, которые можно включить или отключить в зависимости от свойства конфигурации. Это гарантирует, что влияние на производительность будет ограничено конкретными средами или сценариями.
- Отслеживание и оценка. Регулярно отслеживайте и измеряйте производительность своего приложения, чтобы убедиться, что накладные расходы, связанные с AOP, находятся в допустимых пределах. Используйте инструменты профилирования для выявления потенциальных узких мест и соответствующей оптимизации реализации АОП.
Аспект регистрации
Давайте создадим простой аспект для регистрации времени выполнения методов в нашем приложении Spring Boot. Это может помочь выявить узкие места в производительности.
Мы можем создать новый класс под названием «LoggingAspect» и аннотировать его с помощью @Aspect
и @Component
, чтобы указать, что это аспект и компонент, управляемый Spring. Мы реализуем выражение pointcut для методов, которые вы хотите измерить. Например, вы можете настроить таргетинг на все общедоступные методы в определенном пакете. Затем мы реализуем рекомендации для измерения времени выполнения и протоколирования результатов.
Вот пример LoggingAspect
:
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Pointcut("execution(public * com.example.myapp..*.*(..))")
public void publicMethods() {}
@Around("publicMethods()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
logger.debug("Method [{}] executed in {} ms", joinPoint.getSignature(), elapsedTime);
return result;
}
}
Методы Pointcut пусты, поскольку их единственной целью является определение выражения pointcut, определяющего точки соединения (т. е. определенные точки выполнения программы), в которых должен применяться аспект. Само тело метода не содержит никакой логики, так как поведение аспекта определяется в методах-советах (например, @Before
, @After
, @Around код> и т. д.).
Метод pointcut служит многократно используемой ссылкой на конкретное выражение pointcut, что упрощает его обслуживание и обновление при необходимости. Ссылаясь на метод pointcut в своих методах совета, вы можете применить одно и то же выражение pointcut к нескольким советам, не дублируя выражение. Ниже мы увидим это в действии.
После реализации LoggingAspect
мы можем запустить наше приложение и просмотреть журналы. Теперь мы должны увидеть время выполнения для каждого целевого метода как профилировщик для бедняков. Как и обычный профилировщик, этот инструмент имеет много недостатков и влияет на наблюдаемое приложение. Однако мы можем извлечь ценные данные, если настроим это правильно.
Регистрация всех методов
Одной из распространенных проблем, с которыми мы сталкиваемся в крупных проектах, являются ненадежные тесты и сбои. Это особенно сложно понять, поскольку у нас может не хватить данных регистрации.
Добавление журналов ко всему приложению в большинстве случаев нецелесообразно, и нам нужны такие журналы только для определенных выполнений CI. Мы бы не хотели перегружать лог в продакшене или даже поддерживать такой код логирования. Однако знание параметров, полученных каждым методом, и его возвращаемого значения может привести к пониманию ошибки постфактум.
В следующем коде показан такой регистратор, который может распечатать каждую запись/выход, а также аргументы или возвращаемые значения:
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Pointcut("execution(public * com.example.myapp..*.*(..))")
public void publicMethods() {}
@Around("publicMethods()")
public Object logMethodEntryAndExit(ProceedingJoinPoint joinPoint) throws Throwable {
// Log method entry and arguments
Object[] args = joinPoint.getArgs();
logger.debug("Entering method [{}] with arguments: {}", joinPoint.getSignature(), Arrays.toString(args));
// Execute the target method and capture the return value
Object result = joinPoint.proceed();
// Log method exit and return value
logger.debug("Exiting method [{}] with result: {}", joinPoint.getSignature(), result);
// Return the result
return result;
}
}
Верхушка айсберга
Ведение журнала для отслеживания производительности или входа/выхода метода — это мощное, но простое средство. Мы можем пойти гораздо глубже. Мы можем создать аспект для регистрации входящих HTTP-запросов и ответов. Этот аспект перехватывает методы с аннотацией @RequestMapping
, которая обычно используется для обработки HTTP-запросов в приложениях Spring Boot:
@Aspect
@Component
public class RequestResponseLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(RequestResponseLoggingAspect.class);
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMappingMethods() {}
@Around("requestMappingMethods()")
public Object logRequestAndResponse(ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
logger.debug("Request: {} {} - Parameters: {}", request.getMethod(), request.getRequestURI(), request.getParameterMap());
Object result = joinPoint.proceed();
if (result instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) result;
logger.debug("Response: Status {} - Body: {}", responseEntity.getStatusCode(), responseEntity.getBody());
}
return result;
}
}
Другим важным аспектом является создание и уничтожение компонентов. Мы можем создать аспект для регистрации этих событий. Этот аспект перехватывает методы, аннотированные с помощью @PostConstruct
и @PreDestroy
, которые не применяются ко всем bean-компонентам, но помогают нам отслеживать применимый код в большом приложении:< /p>
@Aspect
@Component
public class BeanLifecycleLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(BeanLifecycleLoggingAspect.class);
@Pointcut("@annotation(javax.annotation.PostConstruct)")
public void postConstructMethods() {}
@Pointcut("@annotation(javax.annotation.PreDestroy)")
public void preDestroyMethods() {}
@After("postConstructMethods()")
public void logBeanInitialization(JoinPoint joinPoint) {
logger.debug("Bean Initialized: {}", joinPoint.getTarget().getClass().getSimpleName());
}
@Before("preDestroyMethods()")
public void logBeanDestruction(JoinPoint joinPoint) {
logger.debug("Bean Destroyed: {}", joinPoint.getTarget().getClass().getSimpleName());
}
}
Мы даже можем регистрировать события внедрения зависимостей. Этот аспект перехватывает методы с аннотацией @Autowired
. Однако он не отслеживает внедрение конструктора, но мы можем использовать его для отслеживания типа экземпляра, внедряемого в bean-компонент:
@Aspect
@Component
public class DependencyInjectionLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(DependencyInjectionLoggingAspect.class);
@Pointcut("@annotation(org.springframework.beans.factory.annotation.Autowired)")
public void autowiredMethods() {}
@Before("autowiredMethods()")
public void logDependencyInjection(JoinPoint joinPoint) {
logger.debug("Autowired: {} - Target: {}", joinPoint.getSignature(), joinPoint.getTarget().getClass().getSimpleName());
}
}
Заключительное слово
АОП — это фантастический инструмент отладки. В этом посте я упустил много важных идей и злоупотребил логированием. При отслеживании производительности лучше было бы накапливать значения, а затем регистрировать статистику в конце, тем самым устраняя накладные расходы регистратора (которые влияют на результаты).
Единственное, что я рекомендую, это отключить это по умолчанию. Важно запустить цикл CI без АОП, стоимость слишком велика, и мы можем серьезно повлиять на конечный результат. Нам нужно включить это хирургическим путем, когда нам нужно понять что-то глубокое. В таких ситуациях инструменты АОП могут иметь значение при поиске иголки в стоге сена.
:::информация Также опубликовано здесь.
:::
Оригинал