前言
本文主要介绍SpringApplication在初始化的过程中,执行的两个动作,loadOptions和initMessageSource,loadOptions主要是控制是否允许重复调用刷新配置;initMessageSource主要初始化消息源,根据不同的地区返回不同的消息模板,一般用web用于支持i18n。
loadOptions
public static final String OPTIONS_BEAN_NAME = "contextOptions";
private void loadOptions() {
try {
this.contextOptions = (ContextOptions) getBean(OPTIONS_BEAN_NAME);
} catch (NoSuchBeanDefinitionException ex) {
logger.info("No options bean (\"" + OPTIONS_BEAN_NAME + "\") found: using default");
this.contextOptions = new ContextOptions();
}
}
先来看loadOptions方法,首先是从bean工厂中获取名称为contextOptions的bean,由于xml没有配置,此时会抛异常。那么spring的骚操作开始,进入异常,默认创建ContextOptions。
public class ContextOptions {
private boolean reloadable = true;
public boolean isReloadable() {
return reloadable;
}
public void setReloadable(boolean reloadable) {
this.reloadable = reloadable;
}
}
这个类很简单,只有一个boolean类型的属性reloadable,看名字跟加载相关。如果使用idea可以按住ctrl+鼠标左键,看下哪里使用,最后发现在refresh方法入口处用到,用于判断能不能重复加载配置,其实就是能不能重复执行refresh方法。
public final void refresh() throws ApplicationContextException, BeansException {
if (this.contextOptions != null && !this.contextOptions.isReloadable())
throw new ApplicationContextException("Forbidden to reload config");
.....省略.....
}
initMessageSource
在介绍initMessageSource方法之前,先假设有个需求:针对不同的国家和不同的消息编号返回不同的消息模板,你会怎么做?
首先有一个容器来存放消息模板,想要根据地区和编号获取模板(如上图),使用数组可不可以?可以,但每次匹配的时候需要变量数组,性能不高,时间复杂度O(n)。由于是一个映射关系,那么可以考虑使用map,时间复杂度O(1)。选好容器,地区和编号作为key,value就是消息模板。写出来的代码如下:
public static void main(String[] args){
Map<String,String> map = new HashMap<>();
String code = "110";
map.put("Chinese."+code,"我是{0}");
map.put( + "USA."+code,"I am {0}");
MessageFormat format = new MessageFormat(map.get( "Chinese."+code));
System.out.println(format.format(new String[]{"AAA"}));
MessageFormat format1 = new MessageFormat("USA."+code));
System.out.println(format1.format(new String[]{"AAA"}));
}
该功能主要分两步:
第一步往消息里面添加模板;
第二步使用的时候拿到模板然后格式化。
上面是自己定义的地区,那有没有一个统一的规范呢?在java中一个类Locale,定义了很多地区,我们简单看看。相比于我们自己定义的,Locale适用性很高,可以说是jdk定义的标准,只要用java,那么都是这样。于是代码调整为
Map<String,String> map = new HashMap<>();
String code = "110";
map.put(Locale.SIMPLIFIED_CHINESE.toString() + "."+code,"我是{0}");
map.put(Locale.US.toString() + "."+code,"I am {0}");
MessageFormat format = new MessageFormat(map.get(Locale.SIMPLIFIED_CHINESE.toString() + "."+code));
System.out.println(format.format(new String[]{"中国人"}));
MessageFormat format1 = new MessageFormat(map.get(Locale.US.toString() + "."+code));
System.out.println(format1.format(new String[]{"Ammerica "}));
有了上面的铺垫,接下来我们看看initMessageSource方法干了什么?还是spring的骚操作,抛异常默认创建StaticMessageSource。
private void initMessageSource() {
try {
this.messageSource = (MessageSource) getBean(MESSAGE_SOURCE_BEAN_NAME);
}
catch (NoSuchBeanDefinitionException ex) {
this.messageSource = new StaticMessageSource();
}
}
什么时候会使用到呢?来看看getMessage方法,可以发现,跟我们写的差不多,只是将其封装起来而已,比如添加消息模板和解析key,另外对format进行了缓存,避免每次都创建,具体的大家看下源码就知道了。
public final String getMessage(String code, Object args[], Locale locale) throws NoSuchMessageException {
String mesg = resolve(code, locale); // 根据code和locale找到模板
if (locale == null)
locale = defaultLocale;
MessageFormat format = null;
String formatKey = messageKey(locale, code);
synchronized (formats) {
format = (MessageFormat) formats.get(formatKey);
if (format == null) {
format = new MessageFormat(escape(mesg));
formats.put(formatKey, format);
}
}
return (format.format(args));
}
// messages中存放着各种模板
protected String resolve(String code, Locale locale) {
return (String) this.messages.get(messageKey(locale, code));
}
题外话:不管formats中是否已经存在MessageFormat都直接上锁,其实可以优化为DCL(Double Check Lock),如果MessageFormat存在则直接使用,没必要加锁,代码如下
public final String getMessage(String code, Object args[], Locale locale) throws NoSuchMessageException {
String mesg = resolve(code, locale); // 根据code和locale找到模板
if (locale == null)
locale = defaultLocale;
MessageFormat format = null;
String formatKey = messageKey(locale, code);
format = (MessageFormat) formats.get(formatKey); // 存在则直接使用,没必要进入锁
if(format == null){
synchronized (formats) {
format = (MessageFormat) formats.get(formatKey);
if (format == null) {
format = new MessageFormat(escape(mesg));
formats.put(formatKey, format);
}
}
}
return (format.format(args));
}
MessageSource主要是针对国际化,我们很少用到,除非涉及到境外的业务,所以我们简单看一下就可以, 看不懂也没有关系,这个方法并不重要,不影响我们对整体框架了解。
总结
主要介绍loadOptions和initMessageSource两个方法的逻辑,loadOptions用于控制是否允许重复加载和刷新配置,initMessageSource用于初始化消息源或者说消息模板,提到synchronized使用DCL优化。