Practice of Java multithreading context transfer in complex scenarios

1, Introduction

Starting from India, overseas shopping malls will gradually have the demands of some other countries. At this time, we need to make a transformation for the current shopping malls, which can support shopping malls in many countries. Here, many problems will be involved, such as multilingualism, multi country, multi time zone, localization and so on. In the case of multiple countries, how to pass on the identified national information layer by layer until the last step of code execution. There are even some multithreaded scenarios to deal with.

2, Background technology

2.1 ThreadLocal

ThreadLocal is the easiest to think of. After the entry identifies the country information, it will be thrown into ThreadLocal, so that subsequent codes, redis, DB, etc. can be used for country differentiation.

Here is a brief introduction to 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);
}
  • Each Thread thread has its own threadLocals(ThreadLocalMap), which has a weakly referenced Entry(ThreadLocal,Object).
  • The get method first passes thread Currentthread gets the current thread, then gets the threadLocals(ThreadLocalMap) of the thread, and then gets the value stored by the current thread from the Entry.

  • Change the value value corresponding to the Entry in the threadLocals(ThreadLocalMap) of the current thread when set ting the value.

In actual use, in addition to the synchronous method, there are scenes of asynchronous thread processing. At this time, it is necessary to pass the contents of ThreadLocal from the parent thread to the child thread. What should I do?

No hurry. Java and InheritableThreadLocal will help us solve this problem.

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, boolean)
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  • The InheritableThreadLocal operation uses the inheritableThreadLocals variable instead of the threadLocals variable of the ThreadLocal operation.
  • When creating a new thread, it will check the parent in the parent thread Whether the inheritablethreadlocals variable is null. If it is not null, copy a copy of parent The data of inheritablethreadlocals to this. Of the child thread Inheritablethreadlocals.

  • Because the getMap(Thread) and CreateMap() methods are copied to directly operate inheritableThreadLocals, it is possible to obtain the ThreadLocal value of the parent thread in the child thread.

Now, when using multithreading, it is done through thread pool. Can I use InheritableThreadLocal at this time? Will there be any problem? Let's take a look at the execution of the following code:

  • test

static InheritableThreadLocal<String> 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");// Set new value
 
    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");// Set new value in child thread
 
 
        }
    });
 
    TimeUnit.SECONDS.sleep(1);
    inheritableThreadLocal.set("i am a new inherit parent");// The main thread sets a new value
 
    executorService.execute(new Runnable() {
        @Override
        public void run() {
 
            System.out.println(inheritableThreadLocal.get());
        }
    });
}
 
 
i am a inherit parent
i am a old inherit parent

Looking at the first execution result, I find that the value set by the main thread for the second time has not been changed, or the value set for the first time is "i am a inherit parent". What is the reason?

Looking at the execution result of the second example, it is found that the value of "i am a old inherit parent" set in the first task is printed in the second task. What is the reason for this?

Looking back at the above source code, in the case of thread pool, the data in inheritableThreadLocals will be copied from the parent thread when the thread is created for the first time, Therefore, the first task successfully obtains the "i am a inherit parent" set by the parent thread. When the second task executes, it reuses the thread of the first task and will not trigger the inheritableThreadLocals operation in the copy parent thread. Therefore, even if a new value is set in the main thread, it will not take effect. At the same time, get() The method is to directly operate the inheritableThreadLocals variable, so you can directly get the value set by the first task.

What should I do when I encounter a thread pool?

2.3 TransmittableThreadLocal

Transmittable thread local (TTL) comes in handy at this time. This is an open source component of Alibaba. Let's see how it solves the problem of thread pool. First, let's modify the code based on the above and use TransmittableThreadLocal.


static TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();// Using TransmittableThreadLocal
 
 
public static void main(String[] args) throws InterruptedException {
 
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    executorService = TtlExecutors.getTtlExecutorService(executorService); // Decorate the thread pool with 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");// The child thread sets a new value
 
        }
    });
    System.out.println(transmittableThreadLocal.get());
 
    TimeUnit.SECONDS.sleep(1);
    transmittableThreadLocal.set("i am a new transmittable parent");// The main thread sets a new value
 
    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

After executing the code, it is found that transmittablethreadlocalttlexecutors After getttlexecutorservice (executorservice) decorates the thread pool, each time a task is called, the TransmittableThreadLocal data of the current main thread will be copied to the child thread, and then cleared after execution. At the same time, the changes in the sub thread do not take effect when they return to the main thread. In this way, we can ensure that there is no interference in the execution of each task. How did this happen? Look at the source code.

  • Source code of TtlExecutors and TransmittableThreadLocal


private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    this.capturedRef = new AtomicReference<Object>(capture());
    this.runnable = runnable;
    this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
 
com.alibaba.ttl.TtlRunnable#run
/**
 * wrap method {@link Runnable#run()}.
 */
@Override
public void run() {
    Object captured = capturedRef.get();// Get ThreadLocalMap of thread
    if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }
 
    Object backup = replay(captured);// Temporarily store ThreadLocalMap of current child thread to backup
    try {
        runnable.run();
    } finally {
        restore(backup);// Restore the value corresponding to the modified Threadlocal when the thread is executing
    }
}
 
 
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<TransmittableThreadLocal<?>, Object>) captured;
    Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<TransmittableThreadLocal<?>, 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<TransmittableThreadLocal<?>, 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());
    }
}

You can see the complete sequence diagram of the whole process:

 

OK, now that the problem has been solved, let's take a look at the actual use. There are two kinds of use. The first one involves HTTP requests, Dubbo requests and job s. It adopts data level isolation.

3, Practical application of TTL in overseas shopping malls

3.1 no database, data line + spring MVC

For the user HTTP request, first we need to parse the country number from the url or cookie, then store the country information in the TransmittableThreadLocal, read the country data in the interceptor of MyBatis, make sql transformation, and finally operate the specified country data. In the multi-threaded scenario, the original user-defined thread pool is wrapped with TtlExecutors to ensure that the country information can be transmitted correctly when using the thread pool.

  • HTTP request


public class ShopShardingHelperUtil {
 
    private static TransmittableThreadLocal<String> countrySet = new TransmittableThreadLocal<>();
 
    /**
     * Gets the country flag set in threadLocal
     * @return
     */
    public static String getCountry() {
        return countrySet.get();
    }
 
    /**
     * Set the country set in threadLocal
     */
    public static void setCountry (String country) {
        countrySet.set(country.toLowerCase());
    }
 
 
    /**
     * Clear flag
     */
    public static void clear () {
        countrySet.remove();
    }
}
 
 
 
/** The interceptor comprehensively judges the country information of cookie s and URLs and puts it into TransmittableThreadLocal**/
// Set the country flag in the thread
String country = localeContext.getLocale().getCountry().toLowerCase();
 
ShopShardingHelperUtil.setCountry(country);
 
 
/** Customize the thread pool, and wrap the original customized thread pool with TtlExecutors**/
public static Executor getExecutor() {
 
    if (executor == null) {
        synchronized (TransmittableExecutor.class) {
            if (executor == null) {
                executor = TtlExecutors.getTtlExecutor(initExecutor());// Decorate the Executor with TtlExecutors and solve the problem of threadlocal transmission of asynchronous threads in combination with transmittable threadlocal
            }
        }
    }
 
    return executor;
}
 
 
/** Where the thread pool is actually used, it can be called and executed directly**/
TransmittableExecutor.getExecutor().execute(new BatchExeRunnable(param1,param2));
 
 
 
/** mybatis Interceptor code, using the country information of TransmittableThreadLocal, transforming the original sql, adding country parameters, and distinguishing country data in the sql of addition, deletion, modification and query**/
public Object intercept(Invocation invocation) throws Throwable {
 
    StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
    BoundSql boundSql = statementHandler.getBoundSql();
 
    String originalSql = boundSql.getSql();
 
    Statement statement = (Statement) CCJSqlParserUtil.parse(originalSql);
 
    String threadCountry = ShopShardingHelperUtil.getCountry();
 
    // Only when the country in the thread is not empty can it be processed
    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());
    // Calling a method is actually an intercepted method
    Object result = invocation.proceed();
 
    return result;
}

For the Dubbo interface and the HTTP interface that cannot judge the country information, add the country information parameter in the input parameter part, and set the country information to the TransmittableThreadLocal through the interceptor or manually.

For timed task job s, because all countries need to be executed, all countries will be traversed and executed, which can also be solved through simple annotations.

The transformation of this version and the spot check test have basically passed, and the automatic script verification is no problem, but it was not launched due to business development problems.

3.2 sub database + SpringBoot

Later, when building a new National Mall, the database and table scheme was adjusted to an independent database for each country, and the overall development framework was upgraded to SpringBoot. We upgraded this scheme. The overall idea is the same, but the implementation details are slightly different.

The asynchrony in SpringBoot is generally realized through the annotation @ Async, which is wrapped by a custom thread pool. When used, judge the writing country information of the locale information in the HTTP request, and then complete the operation of cutting DB.

For the Dubbo interface and the HTTP interface that cannot judge the country information, add the country information parameter in the input parameter part, and set the country information to the TransmittableThreadLocal through the interceptor or manually.


@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() {
        // Decorate the Executor with TtlExecutors and solve the problem of threadlocal transmission of asynchronous threads in combination with transmittable 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 : Localized information context 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);
            }
        };
        // Set the country flag in the thread
        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);
            }
        });
    }

For timed task job s, because all countries need to be executed, all countries will be traversed and executed, which can also be solved through simple annotation and AOP.

4, Summary

From the perspective of business expansion, this paper expounds how to transition from ThreadLocal to inheritable ThreadLocal in complex business scenarios, and then solve practical business problems through transmittable ThreadLocal. Because overseas business is advancing in continuous exploration and technology is evolving in continuous exploration. In the face of this complex and changeable situation, our response strategy is to internationalize first and then localize. more global can be more local. Multi Country isolation is only the most basic starting point of internationalization. There are still many businesses and technologies waiting for us to challenge in the future.

Author: vivo official website mall development team

Tags: Java Dubbo Mybatis Redis Spring Spring Boot entry

Posted by joshmmo on Tue, 26 Apr 2022 07:58:23 +0300