海外商城從印度做起,慢慢的會(huì )有一些其他國家的訴求,這個(gè)時(shí)候需要我們針對當前的商城做一個(gè)改造,可以支撐多個(gè)國家的商城,這里會(huì )涉及多個(gè)問(wèn)題,多語(yǔ)言,多國家,多時(shí)區,本地化等等。在多國家的情況下如何把識別出來(lái)的國家信息傳遞下去,一層一層直到代碼執行的最后一步。甚至還有一些多線(xiàn)程的場(chǎng)景需要處理。
ThreadLocal是最容易想到了,入口識別到國家信息后,丟進(jìn)ThreadLocal,這樣后續代碼、redis、DB等做國家區分的時(shí)候都能使用到。
這里先簡(jiǎn)單介紹一下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);
}
每一個(gè)Thread線(xiàn)程都有屬于自己的threadLocals(ThreadLocalMap),里面有一個(gè)弱引用的Entry(ThreadLocal,Object)。
get方法首先通過(guò)Thread.currentThread得到當前線(xiàn)程,然后拿到線(xiàn)程的threadLocals(ThreadLocalMap),再從Entry中取得當前線(xiàn)程存儲的value。
set值的時(shí)候更改當前線(xiàn)程的threadLocals(ThreadLocalMap)中Entry對應的value值。
實(shí)際使用中除了同步方法之外,還有起異步線(xiàn)程處理的場(chǎng)景,這個(gè)時(shí)候就需要把ThreadLocal的內容從父線(xiàn)程傳遞給子線(xiàn)程,這個(gè)怎么辦呢?
不急,Java 還有InheritableThreadLocal來(lái)幫我們解決這個(gè)問(wèn)題。
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);
InheritableThreadLocal操作的是inheritableThreadLocals這個(gè)變量,而不是ThreadLocal操作的threadLocals變量。
創(chuàng )建新線(xiàn)程的時(shí)候會(huì )檢查父線(xiàn)程中parent.inheritableThreadLocals變量是否為null,如果不為null則復制一份parent.inheritableThreadLocals的數據到子線(xiàn)程的this.inheritableThreadLocals中去。
因為復寫(xiě)了getMap(Thread)和CreateMap()方法直接操作inheritableThreadLocals,這樣就實(shí)現了在子線(xiàn)程中獲取父線(xiàn)程ThreadLocal值。
現在在使用多線(xiàn)程的時(shí)候,都是通過(guò)線(xiàn)程池來(lái)做的,這個(gè)時(shí)候用InheritableThreadLocal可以嗎?會(huì )有什么問(wèn)題嗎?先看下下面的代碼的執行情況:
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");// 設置新的值 executorService.execute(new Runnable() {@Override
public void run() {System.out.println(inheritableThreadLocal.get());
}
});
}
i am a inherit parent
i am a inherit parent
這里看第一個(gè)執行結果,發(fā)現主線(xiàn)程第二次設置的值,沒(méi)有改掉,還是第一次設置的值“i am a inherit parent”,這是什么原因呢? 再看第二個(gè)例子的執行結果,發(fā)現在第一個(gè)任務(wù)中設置的“i am a old inherit parent"的值,在第二個(gè)任務(wù)中打印出來(lái)了。這又是什么原因呢? 回過(guò)頭來(lái)看看上面的源碼,在線(xiàn)程池的情況下,第一次創(chuàng )建線(xiàn)程的時(shí)候會(huì )從父線(xiàn)程中copy inheritableThreadLocals中的數據,所以第一個(gè)任務(wù)成功拿到了父線(xiàn)程設置的”i am a inherit parent“,第二個(gè)任務(wù)執行的時(shí)候復用了第一個(gè)任務(wù)的線(xiàn)程,并不會(huì )觸發(fā)復制站長(cháng)博客父線(xiàn)程中的inheritableThreadLocals操作,所以即使在主線(xiàn)程中設置了新的值,也會(huì )不生效。同時(shí)get()方法是直接操作inheritableThreadLocals這個(gè)變量的,所以就直接拿到了第一個(gè)任務(wù)設置的值。 那遇到線(xiàn)程池應該怎么辦呢? TransmittableThreadLocal(TTL)這個(gè)時(shí)候就派上用場(chǎng)了。這是阿里開(kāi)源的一個(gè)組件,我們來(lái)看看它怎么解決線(xiàn)程池的問(wèn)題,先來(lái)一段代碼,在上面的基礎上修改一下,使用TransmittableThreadLocal。 執行代碼后發(fā)現,使用TransmittableThreadLocalTtlExecutors.getTtlExecutorService(executorService)裝飾線(xiàn)程池之后,在每次調用任務(wù)的時(shí),都會(huì )將當前的主線(xiàn)程的TransmittableThreadLocal數據copy到子線(xiàn)程里面,執行完成后,再清除掉。同時(shí)子線(xiàn)程里面的修改回到主線(xiàn)程時(shí)其實(shí)并沒(méi)有生效。這樣可以保證每次任務(wù)執行的時(shí)候都是互不干涉的。這是怎么做到的呢?來(lái)看源碼。 TtlExecutors和TransmittableThreadLocal源碼 OK,既然問(wèn)題都解決了,來(lái)看看實(shí)際使用吧,有兩種使用,先看第一種,涉及HTTP請求、Dubbo請求和 job,采用的是數據級別的隔離。 用戶(hù) HTTP 請求,首先我們要從url或者cookie中解析出國家編號,然后在TransmittableThreadLocal中存放國家信息,在 MyBatis 的攔截器中讀取國家數據,進(jìn)行sql改造,最終操作指定的國家數據,多線(xiàn)程場(chǎng)景下用TtlExecutors包裝原有自定義線(xiàn)程池,保障在使用線(xiàn)程池的時(shí)候能夠正確將國家信息傳遞下去。 HTTP 請求 對于 Dubbo 接口和無(wú)法判斷國家信息的 HTTP 接口,在入參部分增加國家信息參數,通過(guò)攔截器或者手動(dòng)set國家信息到TransmittableThreadLocal。 對于定時(shí)任務(wù) job,因為所有國家都需要執行,所以會(huì )把所有國家進(jìn)行遍歷執行,這也可以通過(guò)簡(jiǎn)單的注解來(lái)解決。 這個(gè)版本的改造,點(diǎn)檢測試也基本通過(guò)了,自動(dòng)化腳本驗證也是沒(méi)問(wèn)題的,不過(guò)因為業(yè)務(wù)發(fā)展問(wèn)題最終沒(méi)上線(xiàn)。 后續在建設新的國家商城的時(shí)候,分庫分表方案調整為每個(gè)國家獨立數據庫,同時(shí)整體開(kāi)發(fā)框架升級到SpringBoot,我們把這套方案做了升級,總體思路是一樣的,只是在實(shí)現細節上略有不同。 SpringBoot 里面的異步一般通過(guò)@Async這個(gè)注解來(lái)實(shí)現,通過(guò)自定義線(xiàn)程池來(lái)包裝,使用時(shí)在 HTTP 請求判斷locale信息的寫(xiě)入國家信息,后續完成切DB的操作。 對于 Dubbo 接口和無(wú)法判斷國家信息的 HTTP 接口,在入參部分增加國家信息參數,通過(guò)攔截器或者手動(dòng)set國家信息到TransmittableThreadLocal。 對于定時(shí)任務(wù)job,因為所有國家都需要執行,所以會(huì )把所有國家進(jìn)行遍歷執行,這也可以通過(guò)簡(jiǎn)單的注解和AOP來(lái)解決。 本文從業(yè)務(wù)拓展的角度闡述了在復雜業(yè)務(wù)場(chǎng)景下如何通過(guò)ThreadLocal,過(guò)渡到InheritableThreadLocal,再通過(guò)TransmittableThreadLocal解決實(shí)際業(yè)務(wù)問(wèn)題。因為海外的業(yè)務(wù)在不斷的探索中前進(jìn),技術(shù)也在不斷的探索中演進(jìn),面對這種復雜多變的情況,我們的應對策略是先做國際化,再做本地化,more global才能more local,多國家的隔離只是國際化最基本的起點(diǎn),未來(lái)還有很多業(yè)務(wù)和技術(shù)等著(zhù)我們去挑戰。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");// 子線(xiàn)程中設置新的值
}
});
TimeUnit.SECONDS.sleep(1);
inheritableThreadLocal.set("i am a new inherit parent");// 主線(xiàn)程設置新的值
executorService.execute(new Runnable() { @Override
public void run() {
System.out.println(inheritableThreadLocal.get());
}
});
}
i am a inherit parent
i am a old inherit parent
2.3 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裝飾線(xiàn)程池
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");// 子線(xiàn)程設置新的值
}
});
System.out.println(transmittableThreadLocal.get());
TimeUnit.SECONDS.sleep(1);
transmittableThreadLocal.set("i am a new transmittable parent");// 主線(xiàn)程設置新的值
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
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();// 獲取線(xiàn)程的ThreadLocalMap
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); }
Object backup = replay(captured);// 暫存當前子線(xiàn)程的ThreadLocalMap到backup
try { runnable.run();
} finally { restore(backup);// 恢復線(xiàn)程執行時(shí)被改版的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<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());
}
}
三、 TTL 在海外商城的實(shí)際應用
3.1 不分庫,分數據行 + SpringMVC
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中 **/
// 設置線(xiàn)程中的國家標志
String country = localeContext.getLocale().getCountry().toLowerCase();
ShopShardingHelperUtil.setCountry(country);
/** 自定義線(xiàn)程池,用TtlExecutors包裝原有自定義線(xiàn)程池 **/
public static Executor getExecutor() {
if (executor == null) { synchronized (TransmittableExecutor.class) { if (executor == null) { executor = TtlExecutors.getTtlExecutor(initExecutor());// 用TtlExecutors裝飾Executor,結合TransmittableThreadLocal解決異步線(xiàn)程threadlocal傳遞問(wèn)題
}
}
}
return executor;
}
/** 實(shí)際使用線(xiàn)程池的地方,直接調用執行即可**/
TransmittableExecutor.getExecutor().execute(new BatchExeRunnable(param1,param2));
/** mybatis的Interceptor代碼, 使用TransmittableThreadLocal的國家信息,改造原有sql,加上國家參數,在增刪改查sql中區分國家數據 **/
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();
// 線(xiàn)程中的國家不為空才進(jìn)行處理
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()); // 調用方法,實(shí)際上就是攔截的方法
Object result = invocation.proceed();
return result;
}
3.2 分庫 + SpringBoot
@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解決異步線(xiàn)程threadlocal傳遞問(wèn)題
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);
}
};
// 設置線(xiàn)程中的國家標志
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);
}
});
}
四、總結
聯(lián)系客服