ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

关于springboot服务端实现国际化资源I18N工具类和MessageSource源码解析

2019-07-27 17:37:05  阅读:971  来源: 互联网

标签:return springboot locale request bundle 源码 null 服务端 String


序言:

目前大多数公司都使用前后端分离的架构来搭建自己的web服务器。
这个程序是解决,当后端出现错误,由后端控制发送到前端的程序。

实现方式
@Component
public class I18NUtls {
	private static MessageSource messageSource;
	@Autowired
	public void setMessageSource(MessageSource messageSource) {
		I18NUtls.messageSource = messageSource;
	}
	
	public static String getMessage(String name) {
		HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
		String lan = request.getHeader("Accept-Language");
		if(lan == null) {
			lan = "zh_CN";
		}
		return messageSource.getMessage(name, null, new Locale(lan));
	}
}

spring配置

spring.messages.basename=i18n/messages

messages.properties

test=\u6D4B\u8BD5

test.properties

test=test

测试的action

package com.aming.test;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletRequestAttributes;

@RestController
public class I18NTestAction {
	@RequestMapping("testI18N")
	public String testI18N() {
		return I18NUtls.getMessage("test");
	}
}

在这里插入图片描述

源码解读

一.关于HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();线程安全问题。

首先在前端发送请求时,会经过。

1.FramewokeServlet: processRequest

//请求发来的时候
//获取 RequestAttributes

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//新建requestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//初始化ContextHolders
		initContextHolders(request, localeContext, requestAttributes);

在FramewokeServlet: processRequest 方法中调用了 buildRequestAttributes方法,而我们获取request就是从这个里面获取的。

2. FramewokeServlet: buildRequestAttributes 新建requestAttributes
//返回了一个ServletRequestAttributes
return new ServletRequestAttributes(request, response);

new了一个ServletRequestAttributes

3. ServletRequestAttributes
//返回的ServletRequestAttributes简单的保存了,request和response

public ServletRequestAttributes(HttpServletRequest request, HttpServletResponse response) {
		this(request);
		this.response = response;
	}
public ServletRequestAttributes(HttpServletRequest request) {
		Assert.notNull(request, "Request must not be null");
		this.request = request;
	}
在ServletRequestAttributes将request保存
4. FramewokeServlet:initContextHolders
//将requestAttributes放入了RequestContextHolder
if (requestAttributes != null) {
			RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		}
	然后将requestAttributes放入RequestContextHolder中
5. RequestContextHolder:setRequestAttributes
//将requestAttributes 放入了 requestAttributesHolder 而 
// private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
			new NamedThreadLocal<RequestAttributes>("Request attributes");

public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
		if (attributes == null) {
			resetRequestAttributes();
		}
		else {
			if (inheritable) {
				inheritableRequestAttributesHolder.set(attributes);
				requestAttributesHolder.remove();
			}
			else {
				requestAttributesHolder.set(attributes);
				inheritableRequestAttributesHolder.remove();
			}
		}
	}

使用requestAttributesHolder存储attributes 而attributes中由request
requestAttributesHolder是ThreadLocal对象。可以保证当我们在多线程环境下从RequestContextHolder获取attributes的时候,不会引发线程问题。

6:RequestContextHolder:getRequestAttributes
//从ThreadLocal中取出当前线程的request
public static RequestAttributes getRequestAttributes() {
		RequestAttributes attributes = requestAttributesHolder.get();
		if (attributes == null) {
			attributes = inheritableRequestAttributesHolder.get();
		}
		return attributes;
	}

总结,在FramewokeServlet的processRequest放置了当前线程的request在RequestContextHolder的requestAttributesHolder中,以为requestAttributesHolder为TheadLocal,ThreadLocal是根据当前线程获取值,所以是线程安全。

二. 关于 MeesageSource国际化获取code对应的国际化值的过程

从AbstractMessageSource:getMessage开始看

1.AbstractMessageSource:getMessage
	public final String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
//获取国际化信息
		String msg = getMessageInternal(code, args, locale);
		if (msg != null) {
			return msg;
		}
//没有获取到,获取默认的信息
		String fallback = getDefaultMessage(code);
		if (fallback != null) {
			return fallback;
		}
		throw new NoSuchMessageException(code, locale);
	}

调用AbstractMessageSource:getMessageInternal获取国际化信息

2. AbstractMessageSource:getMessageInternal
	//调用resolveCodeWithoutArguments去获取国际化信息
if (code == null) {
			return null;
		}
		if (locale == null) {
			locale = Locale.getDefault();
		}
		Object[] argsToUse = args;

		if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
			//没有参数就
			String message = resolveCodeWithoutArguments(code, locale);
			if (message != null) {
				return message;
			}
		}

最后放回的是 resolveCodeWithoutArguments

3. AbstractMessageSource:resolveCodeWithoutArguments
	//先获取spring中配置的basename
	//然后根据getResourceBundle获取bundle
	//然后根据code 获取对应的国际化信息
	protected String resolveCodeWithoutArguments(String code, Locale locale) {
		Set<String> basenames = getBasenameSet();
		for (String basename : basenames) {
			ResourceBundle bundle = getResourceBundle(basename, locale);
			if (bundle != null) {
				String result = getStringOrNull(bundle, code);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}

调用了ResourceBundleMessageSource:getStringOrNul

4.getStringOrNul:ResourceBundleMessageSource
return bundle.getString(key);
5. ResourceBundle:getString
return (String) getObject(key);
  1. ResourceBundle :getObject
Object obj = handleGetObject(key);
  1. PropertyResourceBundle:handleGetObject
  private Map<String,Object> lookup;
return lookup.get(key);

最后使用的是lookup.loopup是一个 hashMap

     总结:MessageSource是内部村粗了一个hashmap来存储键值对,
     然后通过hashMap.get获取。
三. 关于PropertyResourceBundle中lookup的初始化
1.实例化hashMap
public PropertyResourceBundle (Reader reader) throws IOException {
        Properties properties = new Properties();
        properties.load(reader);
        lookup = new HashMap(properties);
    }
2.Properties.load:load0
String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
            put(key, value);

Properties 是根据hastable实现的。

总结:
在ResourceBundleMessageSource中存储了一个loopup的hashMap变量。
每次getMessage就是从这个HahsMap中获取。
而这个reader是根据spring.messages.basename 配置的值去实例化的。
四. 关于ResourceBundleMessageSource加载文件过程:接着第二个分析的第3个步骤开始

那么ResourceBundleMessageSource是在什么时候加载文件的呢

4. ResourceBundleMessageSource:getResourceBundle
//锁住当前缓存的cachedResourceBundles保证线程安全
//从baseName中获取当前的 localeMap 
//根据locale获取bundle
//如果没有获取到缓存里面的bundle则通过doGetBundle去本地加载bundle然后再放入缓存当中。
//
protected ResourceBundle getResourceBundle(String basename, Locale locale) {
		if (getCacheMillis() >= 0) {
			
			return doGetBundle(basename, locale);
		}
		else {
				synchronized (this.cachedResourceBundles) {
				Map<Locale, ResourceBundle> localeMap = this.cachedResourceBundles.get(basename);
				if (localeMap != null) {
					ResourceBundle bundle = localeMap.get(locale);
					if (bundle != null) {
						return bundle;
					}
				}
				try {
					ResourceBundle bundle = doGetBundle(basename, locale);
					if (localeMap == null) {
						localeMap = new HashMap<Locale, ResourceBundle>();
						this.cachedResourceBundles.put(basename, localeMap);
					}
					localeMap.put(locale, bundle);
					return bundle;
				}
				catch (MissingResourceException ex) {
					if (logger.isWarnEnabled()) {
						logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
					}
					return null;
				}
			}
		}
	}
5. ResourceBundleMessageSource:doGetBundle
//返回了ResourceBundle.getBundle
protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
		return ResourceBundle.getBundle(basename, locale, getBundleClassLoader(), new MessageSourceControl());
	}
6:ResourceBu ndle.getBundle
//通过findBundle去加载bundle
public static ResourceBundle getBundle(String baseName, Locale targetLocale,
                                           ClassLoader loader, Control control) {
        if (loader == null || control == null) {
            throw new NullPointerException();
        }
        return getBundleImpl(baseName, targetLocale, loader, control);
    }
private static ResourceBundle getBundleImpl(String baseName, Locale locale,
                                                ClassLoader loader, Control control) {
bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);

        return bundle;
}
7.ResourceBundle:findBundle
bundle = loadBundle(cacheKey, formats, control, expiredBundle); 
8. ResourceBundle :findBundleInCache
bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
                                           cacheKey.getLoader(), reload);
9. ResourceBundleMessageSource :newBundle
return super.newBundle(baseName, locale, format, loader, reload);
10. ResourceBundle: newBundle
return loadBundle(new InputStreamReader(stream, encoding));
11. ResourceBundle: loadBundle
protected ResourceBundle loadBundle(Reader reader) throws IOException {
		return new PropertyResourceBundle(reader);
	}
12. PropertyResourceBundle
	//接下来就是第三点 
 public PropertyResourceBundle (Reader reader) throws IOException {
        Properties properties = new Properties();
        properties.load(reader);
        lookup = new HashMap(properties);
    }
总结:message从cash中获取bundle ,缓存(cash)中没有的就去加载bundle,
用synchronized锁住cashbundle来保证线程安全。

大总结:

中上所述,在spring启动的时候,将ResourceBundleMessageSource实例化。
在每次请求到controller的时候 首先保存request到contextholder中。
在调用ResourceBundleMessageSource.getMessage的时候,
实现从caches中获取bundler 匹配到对应的bundl直接从bundl中的lookup(map)中获取值。
否则就去本地通过PropertyResourceBundle构造函数中Properties.load0去加载本地化资源。
加载的资源放置于缓存中。以便下次再来加载的时候更快。

标签:return,springboot,locale,request,bundle,源码,null,服务端,String
来源: https://blog.csdn.net/qq_27471259/article/details/97529231

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有