Отладка Spring Boot с аспектно-ориентированным программированием (АОП) для повышения модульности

Отладка Spring Boot с аспектно-ориентированным программированием (АОП) для повышения модульности

26 апреля 2023 г.

Аспектно-ориентированное программирование (АОП) – это парадигма программирования, направленная на повышение модульности за счет разделения сквозных задач. Проще говоря, это поможет вам сохранить ваш код чистым, модульным и простым в обслуживании. Мы можем использовать АОП для более эффективной и беспрепятственной отладки приложений Spring Boot. В этом посте мы рассмотрим, как использовать АОП для эффективной отладки приложения Spring Boot.

https://youtu.be/6kxpkcEqssw?embedable=true

Концепции АОП

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

* Аспект. Аспект представляет сквозные проблемы или функциональные возможности, которые необходимо применять во всем приложении. * Точка присоединения. Точка присоединения – это конкретная точка в потоке выполнения приложения, к которой можно применить аспект. * Совет. Действие, предпринятое аспектом в определенной точке соединения, называется советом. Существуют разные типы советов, например, до, после, вокруг и после броска. * Pointcut: Pointcut — это набор точек соединения, к которым следует применить аспект.

Я пропущу обсуждение плетения, так как с этим связано много нюансов, и я хочу сосредоточиться на аспектах отладки. Я не хочу превращать это в учебник по АОП. Однако есть один общий аспект АОП, который я хочу обсудить.

Хотя АОП предлагает значительные преимущества с точки зрения модульности, простоты сопровождения и возможностей отладки, важно помнить о возможных последствиях для производительности.

Использование АОП может привести к некоторым накладным расходам, главным образом из-за создания и выполнения прокси-объектов, которые перехватывают вызовы методов и применяют указанные рекомендации. Влияние на производительность может варьироваться в зависимости от количества аспектов, сложности выражений pointcut и типа используемого совета. Например, консультация по вопросам, как правило, обходится дороже с точки зрения эффективности по сравнению с консультациями до и после.

Чтобы свести к минимуму влияние АОП на производительность, рассмотрите следующие рекомендации:

  1. Будьте избирательны. Применяйте АОП к критически важным частям вашего приложения, где он обеспечивает наибольшую ценность. Избегайте использования аспектов для тривиальных или нечастых операций.
  2. Оптимизация выражений pointcut. Убедитесь, что ваши выражения pointcut максимально точны. Это может помочь свести к минимуму количество ненужных перехватов и сократить накладные расходы.
  3. Ограничить выполнение рекомендации. Выберите тип рекомендации, подходящий для вашего варианта использования. Например, по возможности используйте совет до или после совета, а не вокруг совета.
  4. Использовать условные аспекты. Если некоторые аспекты требуются только для целей отладки или разработки, используйте условные аспекты, которые можно включить или отключить в зависимости от свойства конфигурации. Это гарантирует, что влияние на производительность будет ограничено конкретными средами или сценариями.
  5. Отслеживание и оценка. Регулярно отслеживайте и измеряйте производительность своего приложения, чтобы убедиться, что накладные расходы, связанные с 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 без АОП, стоимость слишком велика, и мы можем серьезно повлиять на конечный результат. Нам нужно включить это хирургическим путем, когда нам нужно понять что-то глубокое. В таких ситуациях инструменты АОП могут иметь значение при поиске иголки в стоге сена.

:::информация Также опубликовано здесь.

:::


Оригинал