Spring的refresh方法-initMessageSource番外篇(3

所有的内容都是基于 https://gitee.com/chris_777/spring-boot2.1.3 springboot 源码
main函数入口org.springframework.boot.tests.hibernate52.Hibernate52Application.main


Spring的refresh方法中有一个initMessageSource方法,此方法在注册完Bean的后置处理器调用。我们首先来看看此方法的源码。首先他会判断此时的Bean工厂是否包含名字为messageSource,类型为MessageSource的Bean实例或者Bean对应的BeanDefinition信息。若包含则获取messageSource为名的Bean对象,否则创建一个DelegatingMessageSource对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// Make MessageSource aware of parent MessageSource.
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// Only set parent context as parent MessageSource if no parent MessageSource
// registered already.
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// Use empty MessageSource to be able to accept getMessage calls.
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}

我们来看一下MessageSource接口的代码 有三个方法。我目前实践第二个方法,其中code我们会用来当做翻译文件的key,用来当做错误码。第二个参数没有用到,第三个Locale参数,是一个语言对象。

1
2
3
4
5
6
7
8
9
10
public interface MessageSource {

@Nullable
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

}

这个方法其实是用来初始中英文翻译的(可能还有其它场景,但是我目前只知道这一个)。那么咱们可以创建一个名为messageSource的Bean,来实现翻译效果。show code!

创建三个文件,一会看源码就知道为啥是三个文件,其实可以根据自身情况灵活创建
1681117205866-b719716e-5f98-4791-8333-05c41a28d9d2-1681654409198

1
7=MyMessageSource
1
7=翻译测试

因为Spring有实现MessageSource接口的类,我就拿来用了:)
org.springframework.context.support.ReloadableResourceBundleMessageSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component("messageSource")
public class MyMessageSource extends ReloadableResourceBundleMessageSource {

public MyMessageSource() {
super();
/**
* 这里的文件名为啥只有一个?{@link ReloadableResourceBundleMessageSource#getMergedProperties(java.util.Locale)}
* 查看上面的方法就好了 他会加载三种文件名的文件 default、default+"_"+lang、default+"_"+lang+"_"+country
*/
this.setBasenames("/i18n/default");
Properties properties = new Properties();
//中文文件必须设置编码,不然乱码.这里的key就是文件名,可以查看上面的源码就可以看见了
properties.setProperty("/i18n/default_zh_CN", "UTF-8");
this.setFileEncodings(properties);
}
}

添加测试类,进行测试。我没有写单元测试,简单的测试了一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component
public class MyMessageSourceTest {
@Autowired
private MessageSource MessageSource;

@PostConstruct
public void init() {
System.out.println("自定义国际化测试>>>>>>>");
String message = MessageSource.getMessage("7", new Object[]{}, Locale.SIMPLIFIED_CHINESE);
System.out.println("输出中文>>>>>>>" + message);
message = MessageSource.getMessage("7", new Object[]{}, Locale.US);
System.out.println("输出英文>>>>>>>" + message);
}
}

//测试输出结果:
//自定义国际化测试>>>>>>>
//输出中文>>>>>>>翻译测试
//输出英文>>>>>>>MyMessageSource

至于中英文翻译,已经够用了。如果你想看看他源码是如何实现的,那请继续看下去吧,咱们来撸撸源码!
我们从直接从MessageSource第二个方法进去(从MyMessageSourceTest测试类调用的方法打断点进去一步步看比较好)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public final String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
//根据 code args来获取locale所给定的信息
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);
}


protected String getMessageInternal(@Nullable String code, @Nullable Object[] args, @Nullable Locale locale) {
if (code == null) {
return null;
}
//若local为空,则给一个默认的
if (locale == null) {
locale = Locale.getDefault();
}
Object[] argsToUse = args;
// 父类的alwaysUseMessageFormat变量默认为false
// 并且参数也为空
if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {
// Optimized resolution: no arguments to apply,
// therefore no MessageFormat needs to be involved.
// Note that the default implementation still uses MessageFormat;
// this can be overridden in specific subclasses.
//解析code,在没有参数的情况下
//resolveCodeWithoutArguments这个方法由
String message = resolveCodeWithoutArguments(code, locale);
if (message != null) {
return message;
}
}

else {
// Resolve arguments eagerly, for the case where the message
// is defined in a parent MessageSource but resolvable arguments
// are defined in the child MessageSource.
//这里会去解析参数ReloadableResourceBundleMessageSource类重写
//具体代码请看新的代码块
argsToUse = resolveArguments(args, locale);

//然后调用resolveCode方法
MessageFormat messageFormat = resolveCode(code, locale);
if (messageFormat != null) {
synchronized (messageFormat) {
return messageFormat.format(argsToUse);
}
}
}

// Check locale-independent common messages for the given message code.
Properties commonMessages = getCommonMessages();
if (commonMessages != null) {
String commonMessage = commonMessages.getProperty(code);
if (commonMessage != null) {
return formatMessage(commonMessage, args, locale);
}
}

// Not found -> check parent, if any.
return getMessageFromParent(code, argsToUse, locale);
}

org.springframework.context.support.ReloadableResourceBundleMessageSource#resolveCodeWithoutArguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
protected String resolveCodeWithoutArguments(String code, Locale locale) {
//cacheMillis变量小于0,默认是小于0的
//我本次测试是走的这个逻辑
if (getCacheMillis() < 0) {
//合并Properties,往下看详细代码
PropertiesHolder propHolder = getMergedProperties(locale);
//根据code获取对象的消息
//直接调用 java.util.Properties#getProperty 返回的结果
String result = propHolder.getProperty(code);
if (result != null) {
return result;
}
}
//-------代码省略
}


protected PropertiesHolder getMergedProperties(Locale locale) {
//从缓存中获取
PropertiesHolder mergedHolder = this.cachedMergedProperties.get(locale);
//有就直接返回,第一次进来肯定是没有的
if (mergedHolder != null) {
return mergedHolder;
}
//创建一个新的Properties
Properties mergedProps = newProperties();
long latestTimestamp = -1;
//将设置的Basename转为数组
//我们会在声明messageSource Bean时,有这样一行代码 this.setBasenames("/i18n/default");
String[] basenames = StringUtils.toStringArray(getBasenameSet());
//从尾部开始
for (int i = basenames.length - 1; i >= 0; i--) {
//计算所有的fileName 此时的basenames[i]=/i18n/default,往下看详细代码
List<String> filenames = calculateAllFilenames(basenames[i], locale);
//从尾部开始便利
for (int j = filenames.size() - 1; j >= 0; j--) {
String filename = filenames.get(j);
//往下看详细代码
PropertiesHolder propHolder = getProperties(filename);
if (propHolder.getProperties() != null) {
mergedProps.putAll(propHolder.getProperties());
if (propHolder.getFileTimestamp() > latestTimestamp) {
latestTimestamp = propHolder.getFileTimestamp();
}
}
}
}
mergedHolder = new PropertiesHolder(mergedProps, latestTimestamp);
//放进缓存中
PropertiesHolder existing = this.cachedMergedProperties.putIfAbsent(locale, mergedHolder);
if (existing != null) {
mergedHolder = existing;
}
return mergedHolder;
}


protected List<String> calculateAllFilenames(String basename, Locale locale) {
//从缓存中获取basename
Map<Locale, List<String>> localeMap = this.cachedFilenames.get(basename);
if (localeMap != null) {
//如果有就返回
List<String> filenames = localeMap.get(locale);
if (filenames != null) {
return filenames;
}
}
List<String> filenames = new ArrayList<>(7);
//集合里面添加计算出来的Filenames,往下看详细代码
//执行完之后,此时集合中有两个数据 /i18n/default_zh,/i18n/default_zh_CN
filenames.addAll(calculateFilenamesForLocale(basename, locale));
//父类的fallbackToSystemLocale变量默认为ture
//并且locale不等于默认的local
if (isFallbackToSystemLocale() && !locale.equals(Locale.getDefault())) {
List<String> fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault());
for (String fallbackFilename : fallbackFilenames) {
if (!filenames.contains(fallbackFilename)) {
// Entry for fallback locale that isn't already in filenames list.
filenames.add(fallbackFilename);
}
}
}
//加入默认的basename
//此时集合中有三个元素 /i18n/default_zh,/i18n/default_zh_CN,/i18n/default
//这也就是为啥会有这三个文件了
filenames.add(basename);
//放入缓存
if (localeMap == null) {
localeMap = new ConcurrentHashMap<>();
Map<Locale, List<String>> existing = this.cachedFilenames.putIfAbsent(basename, localeMap);
if (existing != null) {
localeMap = existing;
}
}
localeMap.put(locale, filenames);
return filenames;
}

protected List<String> calculateFilenamesForLocale(String basename, Locale locale) {
List<String> result = new ArrayList<>(3);
//语言
String language = locale.getLanguage();
//城市
String country = locale.getCountry();
String variant = locale.getVariant();
StringBuilder temp = new StringBuilder(basename);
//temp的后缀加_
temp.append('_');
if (language.length() > 0) {
//拼接语言 /i18n/default_zh
temp.append(language);
//加入到集合中
result.add(0, temp.toString());
}
//temp的后缀加_
temp.append('_');
if (country.length() > 0) {
//拼接城市 /i18n/default_zh_CN
temp.append(country);
//加入到集合中
result.add(0, temp.toString());
}
//判断是否有variant,本次场景没有
if (variant.length() > 0 && (language.length() > 0 || country.length() > 0)) {
temp.append('_').append(variant);
result.add(0, temp.toString());
}

return result;
}

protected PropertiesHolder getProperties(String filename) {
//缓存中获取
PropertiesHolder propHolder = this.cachedProperties.get(filename);
long originalTimestamp = -2;

//若不为空
if (propHolder != null) {
originalTimestamp = propHolder.getRefreshTimestamp();
//若不刷新 或者 是当前系统时间与缓存时的时间小于刷新的时间,则返回,因为还没到刷新的时间
if (originalTimestamp == -1 || originalTimestamp > System.currentTimeMillis() - getCacheMillis()) {
// Up to date
return propHolder;
}
}
else {
//创建新的PropertiesHolder
propHolder = new PropertiesHolder();
//放入缓存
PropertiesHolder existingHolder = this.cachedProperties.putIfAbsent(filename, propHolder);
if (existingHolder != null) {
propHolder = existingHolder;
}
}

// At this point, we need to refresh...
// 如果需要刷新,并且刷新时间戳大于0.则尝试获取锁,没有获取到则返回旧数据
if (this.concurrentRefresh && propHolder.getRefreshTimestamp() >= 0) {
// A populated but stale holder -> could keep using it.
//尝试获取锁,获取失败,则返回
if (!propHolder.refreshLock.tryLock()) {
// Getting refreshed by another thread already ->
// let's return the existing properties for the time being.
return propHolder;
}
}
else {
//锁住
propHolder.refreshLock.lock();
}
try {
PropertiesHolder existingHolder = this.cachedProperties.get(filename);
//还没到刷新时间
if (existingHolder != null && existingHolder.getRefreshTimestamp() > originalTimestamp) {
return existingHolder;
}
//刷新
return refreshProperties(filename, propHolder);
}
finally {
propHolder.refreshLock.unlock();
}
}

protected PropertiesHolder refreshProperties(String filename, @Nullable PropertiesHolder propHolder) {
long refreshTimestamp = (getCacheMillis() < 0 ? -1 : System.currentTimeMillis());

//获取资源
Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX);
if (!resource.exists()) {
//如果不存在,则加载xml结尾的文件
resource = this.resourceLoader.getResource(filename + XML_SUFFIX);
}
//如果存在
if (resource.exists()) {
long fileTimestamp = -1;
//这里默认是小于0的
if (getCacheMillis() >= 0) {
// Last-modified timestamp of file will just be read if caching with timeout.
try {
fileTimestamp = resource.lastModified();
if (propHolder != null && propHolder.getFileTimestamp() == fileTimestamp) {
if (logger.isDebugEnabled()) {
logger.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified");
}
propHolder.setRefreshTimestamp(refreshTimestamp);
return propHolder;
}
}
catch (IOException ex) {
// Probably a class path resource: cache it forever.
if (logger.isDebugEnabled()) {
logger.debug(resource + " could not be resolved in the file system - assuming that it hasn't changed", ex);
}
fileTimestamp = -1;
}
}
try {
//加载属性
//可以看一下这个loadProperties的代码,中文文件必须要设置编码,不然读出来会是乱码
//所以我添加了 properties.setProperty("/i18n/default_zh_CN", "UTF-8")
Properties props = loadProperties(resource, filename);
//封装
propHolder = new PropertiesHolder(props, fileTimestamp);
}
catch (IOException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not parse properties file [" + resource.getFilename() + "]", ex);
}
// Empty holder representing "not valid".
propHolder = new PropertiesHolder();
}
}

else {
// Resource does not exist.
if (logger.isDebugEnabled()) {
logger.debug("No properties file found for [" + filename + "] - neither plain properties nor XML");
}
// Empty holder representing "not found".
propHolder = new PropertiesHolder();
}
propHolder.setRefreshTimestamp(refreshTimestamp);
//放入缓存中返回
this.cachedProperties.put(filename, propHolder);
return propHolder;
}
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信