大家写代码应该都对异常接触比较多了,但是如何正确的处理好异常,这个就是一个比较重要的事情了。
下面,我就异常、异常消息的国际化作一个简单的介绍,希望给不会处理异常的同学提供点帮助。
一、异常介绍
java里面的异常主要分为运行时异常和检查异常,异常的根类是Throwable,继承类是Error和Exception,运行时异常和检查异常都是继承至Exception。其中,Error是错误,无法恢复的;而Exception是可以处理的,进行恢复的。另外,RuntimeException是运行时才能发生的异常,例如NullPointerException、ArrayIndexOutOfBoundsException,像这类异常我们是不需要捕获的,尽量去避免发生,加一些判断。
我们在写接口时,经常会进行参数校验和业务逻辑校验,这时我们便可以使用异常的方式在各个层间进行处理。然后,对于顶层的异常,我们再进行统一处理。
总之,检查时异常我们都是要处理的,不能直接抛到页面上去。
二、ThreadLocal的使用
ThreadLocal这个类是java.util下面的一个类,它的作用如下:
提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
下面,笔者创建一个LocaleContext类,用来设置和传递语言参数,
LocaleContext:
package org.qiyongkang.spring.locale;
import java.util.Locale;
/**
*
* 语言上下文
*
* @version
* <pre>
* Author Version Date Changes
* qiyongkang 1.0 2017年5月22日 Created
*
* </pre>
* @since 1.
*/
public class LocaleContext {
private static ThreadLocal<Locale> localeContext = new ThreadLocal<Locale>();
public static ThreadLocal<Locale> getLocaleContext() {
return localeContext;
}
public static void setLocale(Locale locale) {
localeContext.set(locale);
}
public static Locale getLocale() {
return localeContext.get();
}
}
三、异常消息的国际化
由于,spring是提供了消息的国际化的,所以这里我们直接使用spring的国际化组件。
spring配置文件(applicationContext.xml)如下:
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>conf/exceptions</value>
<value>conf/message</value>
</list>
</property>
</bean>
<bean id="springMessageLocator" class="org.qiyongkang.spring.locale.SpringMessageLocator"
lazy-init="false"></bean>
SpringMessageLocator类如下:
package org.qiyongkang.spring.locale;
import java.util.Locale;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.NoSuchMessageException;
/**
*
* 消息国际化获取类
*
* @version
* <pre>
* Author Version Date Changes
* qiyongkang 1.0 2017年5月19日 Created
*
* </pre>
* @since 1.
*/
public class SpringMessageLocator implements MessageSourceAware {
private static MessageSource messageSource;
public static String getMessage(String code) {
return messageSource.getMessage(code, null, null, null);
}
public static String getMessage(String code, Locale locale) {
return messageSource.getMessage(code, null, null, locale);
}
public static String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
return messageSource.getMessage(code, args, locale);
}
public static String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
return messageSource.getMessage(code, args, defaultMessage, locale);
}
public static String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
return messageSource.getMessage(resolvable, locale);
}
@Override
public void setMessageSource(MessageSource messageSource) {
SpringMessageLocator.messageSource = messageSource;
}
}
这里spring是如何做到国际化的呢,大家有兴趣可以去研究下底层代码,大致过程就是先根据语言环境找到相应的属性文件,找到相应的消息字符串后,对字符串进行占位符format替换。
属性文件,如下:
其中,en表示的是语言,US表示的是国家,就是根据这两个找到相应的属性文件的。
exceptions_en_US.properties:
02030290001=Field[{0}] can not be null {1}.
exceptions_zh_CN.properties:
02030290001=\u5B57\u6BB5\u3010{0}\u3011\u4E0D\u80FD\u4E3A\u7A7A {1}
然后UnifiedException统一的异常类如下:
package org.qiyongkang.spring.exception;
import java.util.Locale;
import org.qiyongkang.spring.locale.LocaleContext;
import org.qiyongkang.spring.locale.SpringMessageLocator;
/**
*
* 公共异常类
*
* @version
* <pre>
* Author Version Date Changes
* qiyongkang 1.0 2017年5月22日 Created
*
* </pre>
* @since 1.
*/
public class UnifiedException extends RuntimeException {
private static final long serialVersionUID = -6275276608078321733L;
private Locale locale; //语言
private String errorCode; //异常码
private String errorMsg; //异常消息
public UnifiedException() {
}
public UnifiedException(String errorCode, Exception e) {
super(errorCode, e);
this.errorCode = errorCode;
Locale locale = LocaleContext.getLocale();
if (locale == null) {
locale = Locale.getDefault();
}
this.locale = locale;
this.errorMsg = SpringMessageLocator.getMessage(this.errorCode, this.locale);
}
public UnifiedException(String errorCode, Object[] params) {
super(errorCode);
this.errorCode = errorCode;
Locale locale = LocaleContext.getLocale();
if (locale == null) {
locale = Locale.getDefault();
}
this.locale = locale;
this.errorMsg = SpringMessageLocator.getMessage(this.errorCode, params, this.locale);
}
public UnifiedException(String errorCode, Object[] params, Exception e) {
super(errorCode, e);
this.errorCode = errorCode;
Locale locale = LocaleContext.getLocale();
if (locale == null) {
locale = Locale.getDefault();
}
this.locale = locale;
this.errorMsg = SpringMessageLocator.getMessage(this.errorCode, params, this.locale);
}
public UnifiedException(String errorCode, Locale locale) {
super(errorCode);
this.errorCode = errorCode;
this.locale = locale;
this.errorMsg = SpringMessageLocator.getMessage(this.errorCode, this.locale);
}
@Override
public String getLocalizedMessage() {
return errorMsg;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
注意这里,我们是使用ThreadLocal进行语言参数的传递的。另外,我们需要重写getLocalizedMessage方法,这样我们抛出异常后,在控制台就能看到国际化后的消息了,否则只能看到编码了。
最后,我们准备一个测试类LocaleTest,来看看效果:
package org.qiyongkang.spring.locale;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.qiyongkang.spring.exception.ExceptionCode;
import org.qiyongkang.spring.exception.UnifiedException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class LocaleTest extends AbstractJUnit4SpringContextTests {
@Test
public void testLocale() {
// Locale locale = new Locale("en", "US");
Locale locale = new Locale("zh", "CN");
String message = SpringMessageLocator.getMessage(ExceptionCode.SYS_02030290001, new Object[]{"username", 111}, locale);
System.out.println("message:" + message);
}
@Test
public void testException() {
Locale locale = new Locale("en", "US");
LocaleContext.setLocale(locale);
UnifiedException exception = new UnifiedException(ExceptionCode.SYS_02030290001, new Object[]{"username", 111}, new RuntimeException("数据库异常"));
System.out.println("code:" + exception.getErrorCode() + ", msg:" + exception.getErrorMsg());
throw exception;
}
}
运行testException,可以看到:
注意,占位符是使用{0}、{1},带上索引号。
好了,关于异常和消息国际化就介绍到这儿了,大家可以自己研究一下~