Шаблоны проектирования — размышления о шаблонах цепочки ответственности

Java


Стандартная модель цепочки ответственности#

Режим цепочки ответственности: Запрос, отправленный клиентом, клиент сам не знает, какой объект обрабатывается, а напрямую бросает его в цепочку объектов, запрос разделяется в цепочке объектов, и объект сам решает, обрабатывать ли его или не.Цепочка завершается, когда запрос обрабатываетсяОсновная цель состоит в том, чтобы отделить клиентский запрос от получателя, но это отделение слишком тщательное, и получатель может только проверить, является ли это запросом, который он должен обрабатывать.
Стандартная модель цепочки ответственностиЗапрос обрабатывается только одним объектом, после успешного завершения процесса цепочка завершается, и запрос больше не передается. Стандартный режим цепочки ответственности не очень распространен. Этот режим один к одному может быть заменен режимом стратегии в большинстве сценариев, только когда клиент не знает конкретного. Когда исполнитель какой объект, больше подходит цепочка ответственности.
Например: Вы хотите подать заявление на получение сертификата в Китайской Империи, но не знаете, куда идти, поэтому ваш выбор - ссылка, сначала зайдите в Бюро А, Бюро А пропустит вас в Бюро Б, Бюро Б отпустит вас в Бюро С Дождитесь решения вашей проблемы.Конечно, есть и результат бега по пустякам.Это тоже недостаток стандартной цепочки ответственности, в результате чего слишком много ненужных звонков.На самом деле есть не так много сценариев применения стандартной цепочки ответственности, и часто используется модернизированная версия цепочки функций.

функциональная цепочка#

Цепочка функций - это эволюция цепочки ответственности.Существенных изменений в структуре нет, но каждый узел может обрабатывать запрос и после обработки обращаться к следующему, то есть каждый запрос проходит через всю цепочку.Есть много сценариев приложений такого рода. , например, я хочу сделать одну вещь, сначала перейти к A, затем перейти к B и, наконец, перейти к C. Этот пример также иллюстрирует связь между ABC и ABC, которая зависит от порядка, в котором цепочка построена.Кроме того, если каждый шаг не выполняется должным образом, вы можете свободно выбрать выход из цепочки.
Текст не очень понятен, вот несколько реальных примеров кода.

Цепочка фильтров в Java#

Для фильтра это даетсяFilterChainЧтобы сделать комбинированный вызов цепочки, запрошенный запрос и возвращенный ответ фактически являются общей контекстной информацией, и каждый обработанный фильтр можно просмотреть и изменить.

public interface FilterChain {
    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;
}

Реализуйте класс в Tomcat какorg.apache.catalina.core.ApplicationFilterChain, структура его следующая:

Фильтры массива — это так называемая цепочка фильтров, которая использует pos (текущая выполняемая позиция) и n (длина цепочки фильтров) для вызова цепочки.
Так как же сделать так, чтобы узел цепочки выбрал продолжение или остановку выполнения?FilterизdoFilterметод, который принимает цепочку ответственности в качестве параметраFilterChainПродолжайте передавать его и продолжайте вызывать цепочкуdoFilterметод, не вызывайте, если он не продолжается.

void doFilter(ServletRequest request, ServletResponse response,
           FilterChain chain) throws IOException, ServletException;

Цепочка перехвата в Spring Security#

Цепочки в Spring Security на самом деле создаютсяSecurityFilterChainОпределен интерфейс, в котором очень просто выставить цепочку списков.Среди нихmatchesОпределить, обрабатывается ли запрос этой цепочкой.

public interface SecurityFilterChain {

    boolean matches(HttpServletRequest request);

    List<Filter> getFilters();
}

класс на входеorg.springframework.security.web.FilterChainProxyсодержит несколько цепочек.

private List<SecurityFilterChain> filterChains;

Каждая цепочка обрабатывает тип запроса, Spring Security использует простой цикл for, чтобы определить, какую цепочку выполнять.

private List<Filter> getFilters(HttpServletRequest request)  {
      for (SecurityFilterChain chain : filterChains) {
          if (chain.matches(request)) {
              return chain.getFilters();
          }
      }
      return null;
  }

Затем структура очень понятна из-за этого шаблона проектирования (это также является преимуществом после знакомства с шаблоном проектирования, просмотр исходного кода может иметь смысл глобального контроля)

Еще один вопрос, как цепочка исполняется свободно?
Это точно так же, как Java Filter, Spring Security реализуетorg.springframework.security.web.FilterChainProxy.VirtualFilterChainкласс, который также реализуетFilterChainинтерфейс, и логика вызова в нем также согласуется с методом tomcat, подробно останавливаться на нем не будем.

Кроме того, Spring Security также предоставляет способ обмена данными, используяThreadLocalОбеспечьте безопасность потоков и достигните цели обмена данными. Кроме того, здесьSecurityContextHolderStrategyЭто приложение шаблона стратегии и стоит посмотреть.

final class ThreadLocalSecurityContextHolderStrategy implements
		SecurityContextHolderStrategy {
    		
	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();

	public void clearContext() {
		contextHolder.remove();
	}

	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();

		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}

		return ctx;
	}
	public void setContext(SecurityContext context) {
		Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
		contextHolder.set(context);
	}

	public SecurityContext createEmptyContext() {
		return new SecurityContextImpl();
	}
}

Цепочка плагинов в Mybatis#

Плагин в Mybatis использует режим, похожий на цепочку ответственности.Конечно, его также можно назвать режимом цепочки ответственности.В конце концов, идеи похожи.Interceptorреализуется интерфейсом, гдеpluginМетод заключается в присоединении узла цепочки к целевому объекту.

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);
}

Так как же построить эту цепочку?InterceptorChainСуществуют следующие методы,InterceptorChainОн собирается во время строительства и настройки и используется на цели после сжигания.pluginAllметод построения полной цепи.

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
...
}

вpluginОфициальная рекомендацияPlugin.wrap(target, this)метод, который по существу использует шаблон прокси для вложения целевого класса


public static Object wrap(Object target, Interceptor interceptor) {
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> type = target.getClass();
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

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

Цепочки, которые можно широко использовать в развитии бизнеса#

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

public interface RegainAfterRefundOrder {
  /**
   * 退回操作
   * @param bo 该订单,可能是子订单,也可能是主订单,自行判断
   * @param operator 操作人
   * @return true成功
   */
  boolean regain(BizOrderDO bo, Long operator);
}

Далее идет единое управление цепочкой, то есть необходимостьChainЭтот класс управляется и может быть реализован следующим образом, а его цепочка вызовов простоapplyAllPluginЦиклический вызов, этот процесс может обеспечить более гибкий вызов в соответствии с Spring Security и другими методами.
Он может быть разработан как класс, который нельзя изменить после создания, включаяinterceptors, что делает код более надежным.

@Component
public class RefundOrderAndRegainChain {

  private final List<RegainAfterRefundOrder> interceptors = new ArrayList<>();

  public void applyAllPlugin(BizOrderDO bo, Long operator) {
    for (RegainAfterRefundOrder interceptor : interceptors) {
      interceptor.regain(bo, operator);
    }
  }

  public void addInterceptor(RegainAfterRefundOrder interceptor) {
    interceptors.add(interceptor);
  }

  public List<RegainAfterRefundOrder> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

Наконец, сборка цепи осуществляется с помощью ИОЦ, предполагая наличиеRegainCoupon,RegainInventoryCount,RegainInvitationCodeWithDrawДождитесь реализации класса RegainAfterRefundOrder, в свою очередь внедрите инъекцию в класс конфигурации Spring и создайте необходимыеRefundOrderAndRegainChain, Наконец, Цепь может быть непосредственно введена в то место, где это необходимо бизнесу.Для этой логики реализуется развязка и гибкое комбинирование.

@Configuration
public class RefundOrderAndRegainConfig {

  @Bean
  public RefundOrderAndRegainChain paidToRefund(
      RegainInventoryCount regainInventoryCount,
      RegainCoupon regainCoupon,
      RegainPromotionRegistered regainPromotionRegistered,
      RegainInvitationCodeWithDraw regainInvitationCode) {
    RefundOrderAndRegainChain chain = new RefundOrderAndRegainChain();
    chain.addInterceptor(regainInventoryCount);
    chain.addInterceptor(regainPromotionRegistered);
    chain.addInterceptor(regainCoupon);
    chain.addInterceptor(regainInvitationCode);
    return chain;
  }

}

Суть модели цепочки ответственности#

  1. Пусть запросчику все равно, кто конкретный получатель, и ему нужно только получить свои конкретные результаты.
  2. В случае, если запрос соответствует нескольким получателям (например, Spring Security), получатели могут свободно комбинироваться, и гибкость очень высока.
  3. Добавление обработки приемника также требует добавления только одного узла в цепочку и не требует слишком больших изменений.
Спасибо за прочтение этой статьи отБлог Цюй Динавсе права защищены. При перепечатке просьба указывать источник: Блог Цюй Дина (Господин дорогой Can/2018/03/20/…)
Шаблоны проектирования — мысли о шаблонах стратегии