【Spring 源码】ApplicationContext 容器的加载原理(四)

【Spring 源码】ApplicationContext 容器的加载原理(四)



一、初始化 Spring 国际化消息资源

在平常使用的时候,如果需要给使用不同语言的用户设定不同的语言消息,比如设置专门的中文、英文界面。这个时候就不能直接对界面上的各种文字进行硬编码,而是每次选择不同语言的时候,分别加载不同的语言文件,这样才能方便系统的扩展,以及维护。

1. Spring 国际化例子

  • 目录结构:
|-- src.main
	|-- java.com.test
		|-- Main.java
	|-- resources
	    |-- message.properties
	    |-- message_zh_CN.properties
	    |-- MyBean.xml
  • MyBean.xml:
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">  
  
	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">  
	    <property name="basenames">  
	        <list>            
		        <value>message</value>  
	        </list>
        </property>
    </bean>
</beans>
  • Main.java:
public class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("MyBean.xml");  
  
		String message1 = context.getMessage("test1", null, Locale.ROOT);  
		String message2 = context.getMessage("test1", null, Locale.CHINA);  
		  
		System.out.println(message1);  
		System.out.println(message2);
    }  
}
  • message.properties 和 message_zh_CN.properties:
# message.properties
test1=test

# message_zh_CN.properties
test1=测试文件
  • 结果:
test
测试文件

2. 初始化源码

【1】获取用户配置的 MessageSource

主要代码:initMessageSource()

MessageSource 的初始化比较简单,就是查找配置文件中配置的 messageSource 类型的 Bean。并且如果没有找到用户配置的 MessageSource 的话,则会使用 DelegatingMessageSource 作为默认。

protected void initMessageSource() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
        this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
        // 处理 HierarchicalMessageSource 类型的 messageSource
        if (this.parent != null 
	        && this.messageSource instanceof HierarchicalMessageSource) {
            HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
            if (hms.getParentMessageSource() == null) {
                // 设置 parent
				hms.setParentMessageSource(getInternalParentMessageSource());
            }
        }
        // 日志追踪
        if (logger.isTraceEnabled()) {  
            logger.trace("Using MessageSource [" + this.messageSource + "]");  
        }
    }
    else {
		// 如果配置文件中没有注册 messageSource,则使用默认的 messageSource 处理
		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 + "]");
		}
    }
}

【2】获取 message

主要代码:getMessage()

比较重要的流程则是 context.getMessage(),等待获取到 messageSource 以后,就可以通过容器获取对应的消息,比如 context.getMessage("test1", null, Locale.CHINA) 可以获取到 message_zh_CN.properties 文件中配置的内容。

@Override  
public final String getMessage(
	String code, 
	@Nullable Object[] args, 
	Locale locale
) throws NoSuchMessageException {  
	String msg = getMessageInternal(code, args, locale);  
	if (msg != null) {  
	    return msg;  
	}  
	String fallback = getDefaultMessage(code);  
	if (fallback != null) {  
	    eturn fallback;  
	}  
	throw new NoSuchMessageException(code, locale);  
}

@Nullable  
protected String getMessageInternal(
	@Nullable String code, 
	@Nullable Object[] args, 
	@Nullable Locale locale
) {
    if (code == null) {
        return null;
    }
    if (locale == null) {
        locale = Locale.getDefault();
    }
    Object[] argsToUse = args;

	// 如果 args 参数没有值,则使用 resolveCodeWithoutArguments 查找
    if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) {   
        String message = resolveCodeWithoutArguments(code, locale);
        if (message != null) {
           return message;
        }
    }
    else {  
        // 处理传入的参数   
        argsToUse = resolveArguments(args, locale);

		// 通过子类处理消息,将传入的参数填入占位符中
        MessageFormat messageFormat = resolveCode(code, locale);
        if (messageFormat != null) {
            synchronized (messageFormat) {
                return messageFormat.format(argsToUse);
            }
        }
    }
  
    // 如果没有处理成功,会创建一个默认的 MessageFormat 处理
    Properties commonMessages = getCommonMessages();
    if (commonMessages != null) {
        String commonMessage = commonMessages.getProperty(code);
        if (commonMessage != null) {
            return formatMessage(commonMessage, args, locale);
        }
    }
  
    // 如果没有找到相关的消息,则查找父级的
    return getMessageFromParent(code, argsToUse, locale);
}

【3】不带参数获取值

主要代码:resolveCodeWithoutArguments()

获取内容的主要方法在 AbstractMessageSource 的 getMessageInternal() 中的 resolveCodeWithoutArguments()resolveCode() 来获取配置资源。因为在配置文件中指定了 ResourceBundleMessageSource 作为目标调用类,而且目标类中重写了这两个方法,所以最终调用的也是这两个方法。

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;  
}

【4】处理消息并返回 MessageFormat

主要代码:resolveCodeWithoutArguments()

在方法中获取的 MessageFormat 主要用来处理消息和传入的参数。

protected MessageFormat resolveCode(String code, Locale locale) {
    Set<String> basenames = getBasenameSet();
    for (String basename : basenames) {
        // 获取配置资源
        ResourceBundle bundle = getResourceBundle(basename, locale);
        if (bundle != null) {
            // 获取值,并且将参数填入占位符中
	        MessageFormat messageFormat = getMessageFormat(bundle, code, locale);
	        if (messageFormat != null) {
		        return messageFormat;
	        }
        }
    }
    return null;
}

【5】获取消息处理器

主要代码:getBundleImpl()

private static ResourceBundle getBundleImpl(
	String baseName, 
	Locale locale,  
	ClassLoader loader, 
	Control control
) {  
	if (locale == null || control == null) {  
		throw new NullPointerException();  
	}  
   
    CacheKey cacheKey = new CacheKey(baseName, locale, loader);  
    ResourceBundle bundle = null;  
  
    // 查找缓存
    BundleReference bundleRef = cacheList.get(cacheKey);  
    if (bundleRef != null) {  
        bundle = bundleRef.get();  
        bundleRef = null;  
    }  

    // 如果在缓存中找到,则直接返回
    if (isValidBundle(bundle) && hasValidParentChain(bundle)) {  
        return bundle;  
    }  

    boolean isKnownControl = (control == Control.INSTANCE) ||  
                               (control instanceof SingleFormatControl);  
    List<String> formats = control.getFormats(baseName);  
    if (!isKnownControl && !checkList(formats)) {  
        throw new IllegalArgumentException("Invalid Control: getFormats");  
    }  

	// 循环查找,直到找到目标文件返回
    ResourceBundle baseBundle = null;  
    for (Locale targetLocale = locale; targetLocale != null;  
         targetLocale = control.getFallbackLocale(baseName, targetLocale)) {  
        List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);  
        if (!isKnownControl && !checkList(candidateLocales)) {  
            throw new IllegalArgumentException("Invalid Control: getCandidateLocales");  
        }  

		// 通过递归查找配置资源
        bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);  
  
        // 判断获取的配置文件后缀名中是否设置了具体的国别
        // 并且如果配置文件后缀中没有配置国别,则会继续循环
        if (isValidBundle(bundle)) {  
            boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);  
            if (!isBaseBundle 
		            || bundle.locale.equals(locale)  
	                || (candidateLocales.size() == 1  
                    && bundle.locale.equals(candidateLocales.get(0)))) {  
                break;  
            }  

            if (isBaseBundle && baseBundle == null) {  
                baseBundle = bundle;  
            }  
        }  
    }  

	// 没有找到则会抛出异常
    if (bundle == null) {  
        if (baseBundle == null) {  
            throwMissingResourceException(baseName, locale, cacheKey.getCause());  
        }  
        bundle = baseBundle;  
    }  
  
    keepAlive(loader);  
    return bundle;  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瞎叨叨的一天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值