Используйте возможности Spring для элегантного написания бизнес-кода

Spring
Используйте возможности Spring для элегантного написания бизнес-кода

Автор|Тянь Ханг (Шпоры)

В нашей повседневной работе по развитию бизнеса мы считаем, что более или менее сталкивались со следующими сценариями:

  • Когда происходит конкретное событие или действие, необходимо выполнить множество действий по связыванию.Последовательное выполнение занимает слишком много времени, а внедрение промежуточного программного обеспечения для сообщений слишком сложно;

  • Хотите реализовать разные стратегии для разных параметров, что мы часто называем режимом стратегии, но у 10 человек может быть 10 разных способов письма, и они всегда кажутся менее элегантными, когда смешиваются вместе;

  • Ваша собственная система хочет вызвать возможности, предоставляемые другими системами, но другие системы всегда время от времени преподносят вам небольшой «небольшой сюрприз». Она может сообщать об исключении тайм-аута из-за проблем в сети или о внезапном выходе из строя компьютера с распределенным приложением. Механизм повторных попыток должен быть введен изящно и неинвазивно.

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

Элегантная реализация шаблона наблюдателя с помощью Spring

Шаблон наблюдателя определяет зависимость между объектами один ко многим. При изменении состояния объекта все объекты, которые зависят от него, уведомляются и автоматически обновляются. В основном это решает проблему изменения состояния объекта для других связанных объектов. , Сообщите о проблеме, обеспечьте простоту использования и низкую связь. Типичный сценарий приложения: после регистрации пользователя необходимо отправить ему электронное письмо, отправить купоны и другие операции, как показано на следующем рисунке.

После использования шаблона наблюдателя:

После того как UserService завершит свою собственную логику регистрации пользователей, ему нужно только опубликовать событие UserRegisterEvent, не обращая внимания на другую логику расширения. Другие службы могут подписаться на UserRegisterEvent сами по себе, чтобы реализовать пользовательскую логику расширения. Механизм событий Spring в основном состоит из трех частей.

  • ApplicationEvent: реализуйте пользовательские события, наследуя его. Кроме того, источник события может быть получен через его исходный атрибут, а время возникновения может быть получено через атрибут временной метки.
  • ApplicationEventPublisher: публиковать события изменений путем его реализации.
  • ApplicationEventListener: реализуя его, вы можете прослушивать события указанного типа и реагировать на действия. Возьмите регистрацию пользователя выше в качестве примера, чтобы увидеть пример кода. Сначала определите событие регистрации пользователя UserRegisterEvent.
publicclass UserRegisterEvent extends ApplicationEvent {
    /**
     * 用户名
     */
    private String username;
    public UserRegisterEvent(Object source) {
        super(source);
    }
    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }
    public String getUsername() {
        return username;
    }
}

Затем определите класс службы регистрации пользователей, внедряйте интерфейс ApplicationEventPuberheraware и введите в него ApplicateEventPublisher. Как вы можете видеть из кода ниже, после выполнения логики регистрации в публикании (ApplicationEvent Event) метод ApplicationEventPublisher вызывается для публикации события userregisterevent.

@Service
publicclass UserService implements ApplicationEventPublisherAware { // <1>
    private Logger logger = LoggerFactory.getLogger(getClass());
    private ApplicationEventPublisher applicationEventPublisher;
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
    public void register(String username) {
        // ... 执行注册逻辑
        logger.info("[register][执行用户({}) 的注册逻辑]", username);
        // <2> ... 发布
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

Создайте службу почтовых ящиков, реализуйте интерфейс ApplicationListener, задайте интересующие события с помощью универсального E, реализуйте метод onApplicationEvent(E event) и выполните пользовательскую обработку отслеживаемых событий UserRegisterEvent.

@Service
publicclass UserService implements ApplicationEventPublisherAware { // <1>
    private Logger logger = LoggerFactory.getLogger(getClass());
    private ApplicationEventPublisher applicationEventPublisher;
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
    public void register(String username) {
        // ... 执行注册逻辑
        logger.info("[register][执行用户({}) 的注册逻辑]", username);
        // <2> ... 发布
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

Создайте сервис купонов, который отличается от описанного выше метода реализации интерфейса ApplicationListener.В методе добавьте аннотацию @EventListener и установите отслеживаемое событие в UserRegisterEvent. Вот еще один способ его использования.

@Service
publicclass CouponService {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @EventListener// <1>
    public void addCoupon(UserRegisterEvent event) {
        logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }
}

Увидев это, внимательные учащиеся могут подумать о режиме публикации-подписки.На самом деле режим наблюдателя отличается от режима публикации-подписки.Короче говоря, режим публикации-подписки относится к режиму наблюдателя в широком смысле.На этом основании , посредничество канала событий вводится для дальнейшего разделения. Как показано на рисунке ниже, видно, что режим наблюдателя более легкий и обычно используется на одной машине, тогда как режим публикации-подписки относительно тяжелее и обычно используется в сценариях уведомления о сообщениях в распределенной среде.

Используйте Spring Retry, чтобы элегантно представить механизм повторных попыток.

Теперь, когда Spring Retry является автономным пакетом (ранняя часть Spring Batch), вот несколько важных шагов для повторной попытки с использованием среды Spring Retry. Шаг 1. Добавьте пакет зависимостей Spring Retry

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.1.2.RELEASE</version>
</dependency>

Шаг 2. Добавьте аннотацию @EnableRetry к классу, содержащему метод main() в приложении, или добавьте аннотацию @EnableRetry к классу, содержащему @Configuration.Шаг 3. Добавьте аннотацию @Retryable к методу, который вы хотите повторить. (может быть исключение)

@Retryable(maxAttempts=5,backoff = @Backoff(delay = 3000))
public void retrySomething() throws Exception{
    logger.info("printSomething{} is called");
    thrownew SQLException();
}

Стратегия повтора в приведенном выше случае состоит в том, чтобы повторить попытку 5 раз с задержкой в ​​3 секунды каждый раз. Подробную документацию смотрите здесь, основные параметры конфигурации следующие. Среди них легко понять атрибуты exclude, include, maxAttempts и value, более непонятным является атрибут backoff, который также является аннотацией и включает в себя четыре атрибута delay, maxDelay, multiplier и random.

  • задержка: если не установлено, по умолчанию 1 секунда
  • maxDelay: максимальное время ожидания повторной попытки
  • множитель: множитель, используемый для расчета следующего времени задержки (эффективен, если больше 0)
  • random: случайное время ожидания повторной попытки (обычно не используется)

Преимущества Spring Retry очевидны: во-первых, он принадлежит к экосистеме Spring и не будет слишком жестким в использовании, во-вторых, вам нужно будет только добавить аннотации к методам, которые необходимо повторить, и настроить атрибуты стратегии повтора. Код взлома.

Но в то же время есть два основных недостатка: во-первых, поскольку Spring Retry использует расширение Aspect, будет неизбежная яма при использовании внутреннего вызова Aspect — метода, если вызывающий и вызываемый метод аннотированы с помощью @Retryable. в том же классе повторная попытка будет недействительной; во-вторых, механизм повторной попытки Spring поддерживает только перехват исключений, но не может проверить возвращаемое значение и повторить попытку. Если вам нужна более гибкая стратегия повторных попыток, вы можете рассмотреть возможность использования Guava Retry, что также является хорошим выбором.

Элегантное использование функций Spring для завершения шаблона бизнес-стратегии

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

Применимый сценарий таков: большая функция имеет множество различных типов реализаций (классов стратегий), в зависимости от клиента, который решает, какой класс стратегии использовать. Например, приоритетная стратегия заказа, стратегия стыковки логистики и т. Д., Существует еще много сценариев применения.

Чтобы привести простой пример, бизнес-фон выглядит следующим образом: платформа должна выполнять аутентификацию в соответствии с различными службами.Логика аутентификации каждой службы отличается и имеет свой собственный набор независимой логики суждения.Поэтому она должна быть основана на входящий bizType Чтобы выполнить операцию аутентификации, сначала мы определяем интерфейс процессора проверки авторизации следующим образом.

/**
 * 业务权限校验处理器
 */
publicinterface PermissionCheckHandler {
    /**
     * 判断是否是自己能够处理的权限校验类型
     */
    boolean isMatched(BizType bizType);
    /**
     * 权限校验逻辑
     */
    PermissionCheckResultDTO permissionCheck(Long userId, String bizCode);
}
业务1的鉴权逻辑我们假设是这样的:
/**
 * 冷启动权限校验处理器
 */
@Component
publicclass ColdStartPermissionCheckHandlerImpl implements PermissionCheckHandler {
    @Override
    public boolean isMatched(BizType bizType) {
        return BizType.COLD_START.equals(bizType);
    }
    @Override
    public PermissionCheckResultDTO permissionCheck(Long userId, String bizCode) {
        //业务特有鉴权逻辑
    }
}
业务2的鉴权逻辑我们假设是这样的:
/**
 * 趋势业务权限校验处理器
 */
@Component
publicclass TrendPermissionCheckHandlerImpl implements PermissionCheckHandler {
    @Override
    public boolean isMatched(BizType bizType) {
        return BizType.TREND.equals(bizType);
    }
    @Override
    public PermissionCheckResultDTO permissionCheck(Long userId, String bizCode){
        //业务特有鉴权逻辑
    }
}

Может быть много других логик бизнес-аутентификации, которые не будут здесь перечислены Логика реализации должна быть организована, как указано выше. Затем мы подошли к ключевому моменту. Выше мы определили так много стратегий. Как мы должны организовать их элегантно? Это требует использования некоторых функций расширения, предоставляемых Spring. Spring в основном предоставляет нам три типа точек расширения, соответствующих различным компонентам. этапы жизненного цикла:

  • Осведомленный интерфейс
  • BeanPostProcessor
  • InitializingBean и init-метод

В основном мы используем две точки расширения интерфейса Aware и InitializingBean. Основное использование показано в следующем коде. Ключевым моментом является реализация метода setApplicationContext интерфейса ApplicationContextAware и метода afterPropertiesSet интерфейса InitializingBean.

ApplicationContextAware достигает цели, чтобы получить интерфейс ресурса Spring контейнера, чтобы облегчить использование getBeansOfType, он предоставляет метод (метод возвращает тип карты, ключ, соответствующий beanName, значение, соответствующее bean); и объект интерфейса достигается удобно InitializingBean выполняет обработчик пользовательской логики инициализации атрибута класса Service.

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

/**
 * 权限校验服务类
 */
@Slf4j
@Service
publicclass PermissionServiceImpl
    implements PermissionService, ApplicationContextAware, InitializingBean {
    private ApplicationContext applicationContext;
    //注:这里可以使用Map,偷个懒
    private List<PermissionCheckHandler> handlers = new ArrayList<>();
    @Override
    public PermissionCheckResultDTO permissionCheck(ArtemisSellerBizType artemisSellerBizType, Long userId,
                                                    String bizCode) {
        //省略一些前置逻辑
        PermissionCheckHandler handler = getHandler(artemisSellerBizType);
        return handler.permissionCheck(userId, bizCode);
    }
    private PermissionCheckHandler getHandler(ArtemisSellerBizType artemisSellerBizType) {
        for (PermissionCheckHandler handler : handlers) {
            if (handler.isMatched(artemisSellerBizType)) {
                return handler;
            }
        }
        returnnull;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        for (PermissionCheckHandler handler : applicationContext.getBeansOfType(PermissionCheckHandler.class)
            .values()) {
            handlers.add(handler);
            log.warn("load permission check handler [{}]", handler.getClass().getName());
        }
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Конечно, я полагаю, что у многих студентов здесь возникнут сомнения, то есть, когда здесь будет получен bean-компонент-обработчик, все ли bean-компоненты были инициализированы? Будут ли некоторые обработчики, которые еще не были инициализированы?

Ответ — нет, цикл объявления Spring Bean гарантирует это (конечно, предпосылка в том, что сам обработчик не имеет специальной логики инициализации). После фактической проверки все обработчики будут готовы перед операцией инициализации службы.Заинтересованные студенты могут написать проверку кода.Они могут сначала поставить журнал на соответствующий хук, чтобы напрямую вывести результат проверки, а затем поставить отладку точки останова в ключевой точке исходный код Spring Я думаю, что это будет Есть много преимуществ.

Резюме и размышления

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

Но единственное, что нельзя остановить, это прогресс, даже перед лицом дракона нельзя отказываться от сопротивления. Конечно, при составлении требований модифицировать эти коды часто невозможно, это слишком долго и трудоемко, а риск слишком велик. Тогда хотя бы подумайте, как спроектировать код, чтобы избежать такой ситуации в будущем, чтобы в следующий раз не совершить ту же ошибку.

Когда мы на самом деле пишем код, вам нужно обратить внимание на изучение весной, есть ли существующие инструменты и удлинительные точки для нас. С одной стороны, использование пружины предлагает эти функции позволяют нам менее создать колесо, избегая введения других относительно тяжелых библиотек; с другой стороны, пружина для некоторых классов и спецификаций, таких как библиотека JDK, обеспечивает абстрактную упаковку, легкость Использование и лучше, более пригодны потребности разработчиков.