Практика многопоточной передачи контекста Java в сложных сценариях

Java распределенный

Введение

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

2. Фоновая технология

2.1 ThreadLocal

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

Вот краткое введение в ThreadLocal:

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
 
 
/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
 
 
/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
 
 
/**
 * Get the entry associated with key.  This method
 * itself handles only the fast path: a direct hit of existing
 * key. It otherwise relays to getEntryAfterMiss.  This is
 * designed to maximize performance for direct hits, in part
 * by making this method readily inlinable.
 *
 * @param  key the thread local object
 * @return the entry associated with key, or null if no such
 */
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

  • Каждый поток Thread имеет свои собственные threadLocals (ThreadLocalMap), которые содержат Entry со слабыми ссылками (ThreadLocal, Object).

  • Метод get сначала получает текущий поток через Thread.currentThread, затем получает threadLocals (ThreadLocalMap) потока, а затем получает значение, сохраненное текущим потоком, из Entry.

  • При установке значения измените значение, соответствующее Entry в threadLocals (ThreadLocalMap) текущего потока.

В реальном использовании, кроме синхронного метода, есть еще сценарии асинхронной обработки потока, в это время содержимое ThreadLocal нужно передать из родительского потока в дочерний, что делать?

Не волнуйтесь, в Java также есть InheritableThreadLocal, который поможет нам решить эту проблему.

2.2 InheritableThreadLoca

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }
 
    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
 
    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

  • java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, логическое значение)

    if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

  • InheritableThreadLocal оперирует переменной inheritableThreadLocals, а не переменной threadLocals, управляемой ThreadLocal.

  • При создании нового потока он проверит, имеет ли значение переменная parent.inheritableThreadLocals в родительском потоке значение null.Если оно не равно нулю, скопируйте копию данных parent.inheritableThreadLocals в this.inheritableThreadLocals дочернего потока.

  • Поскольку методы getMap(Thread) и CreateMap() переопределены для непосредственного управления inheritableThreadLocals, можно получить значение ThreadLocal родительского потока в дочернем потоке.

Теперь при использовании многопоточности все делается через пул потоков. Можно ли сейчас использовать InheritableThreadLocal? Не будет ли проблем? Давайте посмотрим на выполнение следующего кода:

  • test

    static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {

    ExecutorService executorService = Executors.newFixedThreadPool(1);
    
    inheritableThreadLocal.set("i am a inherit parent");
    executorService.execute(new Runnable() {
        @Override
        public void run() {
    
            System.out.println(inheritableThreadLocal.get());
        }
    });
    
    TimeUnit.SECONDS.sleep(1);
    inheritableThreadLocal.set("i am a new inherit parent");// 设置新的值
    
    executorService.execute(new Runnable() {
        @Override
        public void run() {
    
            System.out.println(inheritableThreadLocal.get());
        }
    });
    

    }

    i am a inherit parent i am a inherit parent

    public static void main(String[] args) throws InterruptedException {

    ExecutorService executorService = Executors.newFixedThreadPool(1);
    
    inheritableThreadLocal.set("i am a inherit parent");
    executorService.execute(new Runnable() {
        @Override
        public void run() {
    
            System.out.println(inheritableThreadLocal.get());
            inheritableThreadLocal.set("i am a old inherit parent");// 子线程中设置新的值
    
    
        }
    });
    
    TimeUnit.SECONDS.sleep(1);
    inheritableThreadLocal.set("i am a new inherit parent");// 主线程设置新的值
    
    executorService.execute(new Runnable() {
        @Override
        public void run() {
    
            System.out.println(inheritableThreadLocal.get());
        }
    });
    

    }

    i am a inherit parent i am a old inherit parent

Глядя на первый результат выполнения здесь, обнаруживается, что значение, установленное основным потоком во второй раз, не было изменено, или значение, установленное в первый раз, равно «я наследующий родитель». В чем причина?

Глядя на результат выполнения второго примера, обнаруживается, что значение «я старый наследующий родитель», установленное в первой задаче, печатается во второй задаче. Что является причиной этого?

Оглядываясь назад на приведенный выше исходный код, в случае пула потоков данные в inheritableThreadLocals будут скопированы из родительского потока, когда поток создается в первый раз, поэтому первая задача успешно получает набор «я». родительским потоком. наследовать родительский», когда выполняется вторая задача, поток первой задачи используется повторно, и операция inheritableThreadLocals в родительском потоке копирования не будет запущена, поэтому даже если в основном задано новое значение поток, это не вступит в силу. В то же время метод get() напрямую оперирует переменной inheritableThreadLocals, поэтому напрямую получает значение, заданное первой задачей.

Что делать, если я столкнулся с пулом потоков?

2.3 TransmittableThreadLocal

TransmittableThreadLocal (TTL) пригодится в это время. Это компонент с открытым исходным кодом Alibaba.Давайте посмотрим, как он решает проблему пулов потоков.Начнем с куска кода, изменим его на основе вышеизложенного и воспользуемся TransmittableThreadLocal.

static TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();// 使用TransmittableThreadLocal
 
 
public static void main(String[] args) throws InterruptedException {
 
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    executorService = TtlExecutors.getTtlExecutorService(executorService); // 用TtlExecutors装饰线程池
 
    transmittableThreadLocal.set("i am a transmittable parent");
    executorService.execute(new Runnable() {
        @Override
        public void run() {
 
            System.out.println(transmittableThreadLocal.get());
            transmittableThreadLocal.set("i am a old transmittable parent");// 子线程设置新的值
 
        }
    });
    System.out.println(transmittableThreadLocal.get());
 
    TimeUnit.SECONDS.sleep(1);
    transmittableThreadLocal.set("i am a new transmittable parent");// 主线程设置新的值
 
    executorService.execute(new Runnable() {
        @Override
        public void run() {
 
            System.out.println(transmittableThreadLocal.get());
        }
    });
}
 
 
i am a transmittable parent
i am a transmittable parent
i am a new transmittable parent

После выполнения кода обнаружено, что после использования TransmittableThreadLocalTtlExecutors.getTtlExecutorService(executorService) для оформления пула потоков каждый раз при вызове задачи данные TransmittableThreadLocal текущего основного потока будут копироваться в дочерний поток, а затем очищаться после выполнение завершено. В то же время модификация в дочернем потоке не вступает в силу, когда он возвращается в основной поток. Это гарантирует, что каждая задача выполняется, не мешая друг другу. Как это делается? Посмотрите на исходный код.

  • Исходный код TtlExecutors и TransmittableThreadLocal

    private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference(capture()); this.runnable = runnable; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; }

    com.alibaba.ttl.TtlRunnable#run /**

    • wrap method {@link Runnable#run()}.

    */ @Override публичный недействительный запуск () { Захваченный объект = захваченныйRef.get();// Получить ThreadLocalMap потока if (захвачено == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(захвачено, null)) { throw new IllegalStateException("Ссылка на значение TTL освобождается после запуска!"); }

    Object backup = replay(captured);// 暂存当前子线程的ThreadLocalMap到backup
    try {
        runnable.run();
    } finally {
        restore(backup);// 恢复线程执行时被改版的Threadlocal对应的值
    }
    

    }

    com.alibaba.ttl.TransmittableThreadLocal.Transmitter#replay

    /**

    • Replay the captured {@link TransmittableThreadLocal} values from {@link #capture()},
    • and return the backup {@link TransmittableThreadLocal} values in current thread before replay.
    • @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()}
    • @return the backup {@link TransmittableThreadLocal} values before replay
    • @see #capture()
    • @since 2.3.0

    */ public static Object replay(Object captured) { @SuppressWarnings("unchecked") Map<TransmittableThreadLocal, Object> capturedMap = (Map, Object>) captured; Map<TransmittableThreadLocal, Object> backup = new HashMap, Object>();

    for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator();
         iterator.hasNext(); ) {
        Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
        TransmittableThreadLocal<?> threadLocal = next.getKey();
    
        // backup
        backup.put(threadLocal, threadLocal.get());
    
        // clear the TTL value only in captured
        // avoid extra TTL value in captured, when run task.
        if (!capturedMap.containsKey(threadLocal)) {
            iterator.remove();
            threadLocal.superRemove();
        }
    }
    
    // set value to captured TTL
    for (Map.Entry<TransmittableThreadLocal<?>, Object> entry : capturedMap.entrySet()) {
        @SuppressWarnings("unchecked")
        TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal<Object>) entry.getKey();
        threadLocal.set(entry.getValue());
    }
    
    // call beforeExecute callback
    doExecuteCallback(true);
    
    return backup;
    

    }

    com.alibaba.ttl.TransmittableThreadLocal.Transmitter#restore

    /**

    • Restore the backup {@link TransmittableThreadLocal} values from {@link Transmitter#replay(Object)}.
    • @param backup the backup {@link TransmittableThreadLocal} values from {@link Transmitter#replay(Object)}
    • @since 2.3.0

    */ public static void restore(Object backup) { @SuppressWarnings("unchecked") Map<TransmittableThreadLocal, Object> backupMap = (Map, Object>) backup; // call afterExecute callback doExecuteCallback(false);

    for (Iterator<? extends Map.Entry<TransmittableThreadLocal<?>, ?>> iterator = holder.get().entrySet().iterator();
         iterator.hasNext(); ) {
        Map.Entry<TransmittableThreadLocal<?>, ?> next = iterator.next();
        TransmittableThreadLocal<?> threadLocal = next.getKey();
    
        // clear the TTL value only in backup
        // avoid the extra value of backup after restore
        if (!backupMap.containsKey(threadLocal)) {
            iterator.remove();
            threadLocal.superRemove();
        }
    }
    
    // restore TTL value
    for (Map.Entry<TransmittableThreadLocal<?>, Object> entry : backupMap.entrySet()) {
        @SuppressWarnings("unchecked")
        TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal<Object>) entry.getKey();
        threadLocal.set(entry.getValue());
    }
    

    }

    Вы можете увидеть полную временную диаграмму всего процесса:

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

    3. Практическое применение TTL в зарубежных торговых центрах

    3.1 Независимо от базы данных, строка данных + SpringMVC

    Пользовательский HTTP-запрос, сначала нам нужно проанализировать номер страны из URL-адреса или файла cookie, затем сохранить информацию о стране в TransmittableThreadLocal, прочитать данные о стране в перехватчике MyBatis, выполнить преобразование sql и, наконец, обработать указанные данные страны, мульти- многопоточный сценарий Затем используйте TtlExecutors для переноса исходного пользовательского пула потоков, чтобы обеспечить правильную передачу информации о стране при использовании пула потоков.

    • HTTP-запрос

      public class ShopShardingHelperUtil {

      private static TransmittableThreadLocal<String> countrySet = new TransmittableThreadLocal<>();
      
      /**
       * 获取threadLocal中设置的国家标志
       * @return
       */
      public static String getCountry() {
          return countrySet.get();
      }
      
      /**
       * 设置threadLocal中设置的国家
       */
      public static void setCountry (String country) {
          countrySet.set(country.toLowerCase());
      }
      
      
      /**
       * 清除标志
       */
      public static void clear () {
          countrySet.remove();
      }
      

      }

      /** Перехватчик всесторонне оценивает информацию о стране в файле cookie и URL-адресе и помещает ее в TransmittableThreadLocal **/ // устанавливаем флаг страны в треде Строка country = localeContext.getLocale().getCountry().toLowerCase();

      ShopShardingHelperUtil.setCountry(country);

      /** Пользовательский пул потоков, оберните исходный пользовательский пул потоков с помощью TtlExecutors **/ общедоступный статический исполнитель getExecutor() {

      if (executor == null) {
          synchronized (TransmittableExecutor.class) {
              if (executor == null) {
                  executor = TtlExecutors.getTtlExecutor(initExecutor());// 用TtlExecutors装饰Executor,结合TransmittableThreadLocal解决异步线程threadlocal传递问题
              }
          }
      }
      
      return executor;
      

      }

      /** Там, где фактически используется пул потоков, просто вызовите его и выполните напрямую**/ TransmittableExecutor.getExecutor().execute(новый BatchExeRunnable(param1,param2));

      /** Код перехватчика mybatis использует информацию о стране TransmittableThreadLocal, преобразует исходный sql, добавляет параметры страны и различает данные страны при добавлении, удалении, изменении и запросе sql **/ Перехват общественного объекта (вызов вызова) выдает Throwable {

      StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
      BoundSql boundSql = statementHandler.getBoundSql();
      
      String originalSql = boundSql.getSql();
      
      Statement statement = (Statement) CCJSqlParserUtil.parse(originalSql);
      
      String threadCountry = ShopShardingHelperUtil.getCountry();
      
      // 线程中的国家不为空才进行处理
      if (StringUtils.isNotBlank(threadCountry)) {
      
          if (statement instanceof Select) {
      
              Select selectStatement = (Select) statement;
              VivoSelectVisitor vivoSelectVisitor = new VivoSelectVisitor(threadCountry);
              vivoSelectVisitor.init(selectStatement);
          } else if (statement instanceof Insert) {
      
              Insert insertStatement = (Insert) statement;
              VivoInsertVisitor vivoInsertVisitor = new VivoInsertVisitor(threadCountry);
              vivoInsertVisitor.init(insertStatement);
      
          } else if (statement instanceof Update) {
      
              Update updateStatement = (Update) statement;
              VivoUpdateVisitor vivoUpdateVisitor = new VivoUpdateVisitor(threadCountry);
              vivoUpdateVisitor.init(updateStatement);
      
          } else if (statement instanceof Delete) {
      
              Delete deleteStatement = (Delete) statement;
              VivoDeleteVisitor vivoDeleteVisitor = new VivoDeleteVisitor(threadCountry);
              vivoDeleteVisitor.init(deleteStatement);
          }
      
      
          Field boundSqlField = BoundSql.class.getDeclaredField("sql");
          boundSqlField.setAccessible(true);
          boundSqlField.set(boundSql, statement.toString());
      } else {
      
          logger.error("----------- intercept not-add-country sql.... ---------" + statement.toString());
      }
      
      logger.info("----------- intercept query new sql.... ---------" + statement.toString());
      // 调用方法,实际上就是拦截的方法
      Object result = invocation.proceed();
      
      return result;
      

      }

    Для интерфейса Dubbo и интерфейса HTTP, которые не могут определить информацию о стране, добавьте параметр информации о стране в разделе входных параметров и установите для информации о стране значение TransmittableThreadLocal через перехватчик или вручную.

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

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

    3.2 Подбиблиотека + SpringBoot

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

    Асинхронность в SpringBoot обычно реализуется через аннотацию **@Async, которая упакована настраиваемым пулом потоков.При ее использовании информация о локали оценивается в HTTP-запросе для записи информации о стране и последующей операции вырезания БД завершена. **

    Для интерфейса Dubbo и интерфейса HTTP, которые не могут определить информацию о стране, добавьте параметр информации о стране в разделе входных параметров и установите для информации о стране значение TransmittableThreadLocal через перехватчик или вручную.

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        return TtlThreadPoolExecutors.getAsyncExecutor();
    }
     
     
    public class TtlThreadPoolExecutors {
     
        private static final String COMMON_BUSINESS = "COMMON_EXECUTOR";
     
        public static final int QUEUE_CAPACITY = 20000;
     
        public static ExecutorService getExecutorService() {
            return TtlExecutorServiceMananger.getExecutorService(COMMON_BUSINESS);
        }
     
        public static ExecutorService getExecutorService(String threadGroupName) {
            return TtlExecutorServiceMananger.getExecutorService(threadGroupName);
        }
     
        public static ThreadPoolTaskExecutor getAsyncExecutor() {
            // 用TtlExecutors装饰Executor,结合TransmittableThreadLocal解决异步线程threadlocal传递问题
            return getTtlThreadPoolTaskExecutor(initTaskExecutor());
        }
     
        private static ThreadPoolTaskExecutor initTaskExecutor () {
            return initTaskExecutor(TtlThreadPoolFactory.DEFAULT_CORE_SIZE, TtlThreadPoolFactory.DEFAULT_POOL_SIZE, QUEUE_CAPACITY);
        }
     
        private static ThreadPoolTaskExecutor initTaskExecutor (int coreSize, int poolSize, int executorQueueCapacity) {
            ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
            taskExecutor.setCorePoolSize(coreSize);
            taskExecutor.setMaxPoolSize(poolSize);
            taskExecutor.setQueueCapacity(executorQueueCapacity);
            taskExecutor.setKeepAliveSeconds(120);
            taskExecutor.setAllowCoreThreadTimeOut(true);
            taskExecutor.setThreadNamePrefix("TaskExecutor-ttl");
            taskExecutor.initialize();
            return taskExecutor;
        }
     
        private static ThreadPoolTaskExecutor getTtlThreadPoolTaskExecutor(ThreadPoolTaskExecutor executor) {
            if (null == executor || executor instanceof ThreadPoolTaskExecutorWrapper) {
                return executor;
            }
            return new ThreadPoolTaskExecutorWrapper(executor);
        }
    }
     
     
     
     
    /**
     * @ClassName : LocaleContextHolder
     * @Description : 本地化信息上下文holder
     */
    public class LocalizationContextHolder {
        private static TransmittableThreadLocal<LocalizationContext> localizationContextHolder = new TransmittableThreadLocal<>();
        private static LocalizationInfo defaultLocalizationInfo = new LocalizationInfo();
     
        private LocalizationContextHolder(){}
     
        public static LocalizationContext getLocalizationContext() {
            return localizationContextHolder.get();
        }
     
        public static void resetLocalizationContext () {
            localizationContextHolder.remove();
        }
     
        public static void setLocalizationContext (LocalizationContext localizationContext) {
            if(localizationContext == null) {
                resetLocalizationContext();
            } else {
                localizationContextHolder.set(localizationContext);
            }
        }
     
        public static void setLocalizationInfo (LocalizationInfo localizationInfo) {
            LocalizationContext localizationContext = getLocalizationContext();
            String brand = (localizationContext instanceof BrandLocalizationContext ?
                    ((BrandLocalizationContext) localizationContext).getBrand() : null);
            if(StringUtils.isNotEmpty(brand)) {
                localizationContext = new SimpleBrandLocalizationContext(localizationInfo, brand);
            } else if(localizationInfo != null) {
                localizationContext = new SimpleLocalizationContext(localizationInfo);
            } else {
                localizationContext = null;
            }
            setLocalizationContext(localizationContext);
        }
     
        public static void setDefaultLocalizationInfo(@Nullable LocalizationInfo localizationInfo) {
            LocalizationContextHolder.defaultLocalizationInfo = localizationInfo;
        }
     
        public static LocalizationInfo getLocalizationInfo () {
            LocalizationContext localizationContext = getLocalizationContext();
            if(localizationContext != null) {
                LocalizationInfo localizationInfo = localizationContext.getLocalizationInfo();
                if(localizationInfo != null) {
                    return localizationInfo;
                }
            }
            return defaultLocalizationInfo;
        }
     
        public static String getCountry(){
            return getLocalizationInfo().getCountry();
        }
     
        public static String getTimezone(){
            return getLocalizationInfo().getTimezone();
        }
     
        public static String getBrand(){
            return getBrand(getLocalizationContext());
        }
     
        public static String getBrand(LocalizationContext localizationContext) {
            if(localizationContext == null) {
                return null;
            }
            if(localizationContext instanceof BrandLocalizationContext) {
                return ((BrandLocalizationContext) localizationContext).getBrand();
            }
            throw new LocaleException("unsupported localizationContext type");
        }
    }
        @Override
        public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
            parseLocaleCookieIfNecessary(request);
            LocaleContext localeContext = new TimeZoneAwareLocaleContext() {
                @Override
                public Locale getLocale() {
                    return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
                }
                @Override
                public TimeZone getTimeZone() {
                    return (TimeZone) request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
                }
            };
            // 设置线程中的国家标志
            setLocalizationInfo(request, localeContext.getLocale());
            return localeContext;
        }
     
        private void setLocalizationInfo(HttpServletRequest request, Locale locale) {
            String country = locale!=null?locale.getCountry():null;
            String language = locale!=null?(locale.getLanguage() + "_" + locale.getVariant()):null;
            LocaleRequestMessage localeRequestMessage = localeRequestParser.parse(request);
            final String countryStr = country;
            final String languageStr = language;
            final String brandStr = localeRequestMessage.getBrand();
            LocalizationContextHolder.setLocalizationContext(new BrandLocalizationContext() {
                @Override
                public String getBrand() {
                    return brandStr;
                }
     
                @Override
                public LocalizationInfo getLocalizationInfo() {
                    return LocalizationInfoAssembler.assemble(countryStr, languageStr);
                }
            });
        }
    

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

    4. Резюме

    С точки зрения развития бизнеса в этой статье объясняется, как перейти на InheritableThreadLocal через ThreadLocal в сложных бизнес-сценариях, а затем решить практические бизнес-задачи с помощью TransmittableThreadLocal. Поскольку зарубежный бизнес развивается в непрерывном исследовании, а технологии также развиваются в непрерывном исследовании, перед лицом этой сложной и изменчивой ситуации наша стратегия реагирования заключается в том, чтобы сначала провести интернационализацию, а затем локализацию, более глобальное может быть более локальным, Изоляция нескольких стран — это лишь самая основная отправная точка для интернационализации, и в будущем нам еще предстоит бросить вызов многим предприятиям и технологиям.

    Автор: команда разработчиков торгового центра официального сайта vivo