问题和解决方法
App因某种原因会抛出异常,我们会记录在log中,同时也要告知用户出了问题,一些错误信息需要显示给用户,这就有国际化的需求。我们不应简单将原始的Exception直接显示给用户:
- 异常可能是在底层,例如仓库读写数据出现异常,中间经过service,controller,才到view。在每个环节中,如何知道异常已经被记录下来,无需再次log?➤ 可以自定义异常,如LoggedException,记录原始的log,然后抛出LoggedException,如果捕获的是LoggedException,我们不需要再次记录。LoggedException中可以将原始的异常作为cause。
- 用户很难明白,另外可能会向黑客泄漏某些可以利用的信息,需要向用户显示有效的说明,并实现国际化。 ➤ 自定义异常,通过实现MessageSourceResolvable接口来实现国际化。
结合问题和解决方式,我们将定义InternationalizedException实现MessageSourceResolvable,然后LoggedException继承它。
页面显示国际化的错误信息
InternationalizedException
public class InternationalizedException extends RuntimeException implements MessageSourceResolvable{
private static final long serialVersionUID = 1L;
private static final Locale DEFAULT_LOCALE = Locale.CHINA;
private final String errorCode;
private final String[] codes;
private final Object[] arguments;
//【1】根据构造函数,public RuntimeException(String message,Throwable cause)。其中errorCode作为向用户显示的信息,我们的目的就是对这一部分进行国际化。
public InternationalizedException(String errorCode,Object ...objects ){
this(null,errorCode,null,objects);
}
public InternationalizedException(Throwable cause, String errorCode,Object ...objects ){
this(cause,errorCode,null,objects);
}
public InternationalizedException( String errorCode,String defaultMessage,Object ...objects ){
this(null,errorCode,defaultMessage,objects);
}
public InternationalizedException(Throwable cause, String errorCode,String defaultMessage,Object ...objects ){
super( defaultMessage == null ? errorCode : defaultMessage, cause);
this.errorCode = errorCode;
this.codes = new String[]{errorCode};
this.arguments = objects;
}
/*【2】国际化过程:我们的目的是将errorCode进行国际化,需要实现三个MessageSourceResolvable接口
* getCodes()和spring:message中的code相同,使用数组,给出查询的顺序,如果第一查不到,查第二个。本例只需一个,填入errorCode即可。
* getArguments()和spring:message中的argument含义相同。
* getDefaultMessage()就是在所有code中查不到,使用的缺省消息,和spring:message中的text含义相同,在本例,我们使用exception.getMessage()
*/
@Override
public String[] getCodes() {
return this.codes;
}
@Override
public Object[] getArguments() {
return this.arguments;
}
@Override
public String getDefaultMessage() {
return this.getMessage();
}
}
LoggedException
LoggedException用来标记这是已经进行log记录的异常,无需再次进行。所以简单地,直接继承InternationalizedException就好了。public class LoggedException extends InternationalizedException{
private static final long serialVersionUID = 1L;
public LoggedException(Throwable cause, String errorCode, String defaultMessage, Object... objects) {
super(cause, errorCode, defaultMessage, objects);
}
public LoggedException(Throwable cause, String errorCode, Object... objects) {
this(cause, errorCode, null, objects);
}
}
jsp中使用MessageSourceResolvable对象
spring:message中支持直接传递MessageSourceResolvable对象,采用message参数。
Controller中的例子代码如下:
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(Map<String, Object> model){
... ...
//【1】模拟有一个异常,并进行了log记录
Exception exception = new Exception("testERROR",new Throwable("E . R . R . O . R"));
logger.error("Log exception {} caused by {} : ", exception.toString(),exception.getCause());
//【2】将其封装为LoggedException(实际也是实现MessageSourceResolvable的InternationalizedException)
// "foo.test"就是国际化中的code,参数为Test和Error message,如果找不到这个code进行国际化,则输出"This is a Error message"
LoggedException loggedException = new LoggedException(exception,
"foo.test",
"This is a Error message",
"Test","Error message");
//【3】将MessageSourceResolvable对象存放在model中,进而传递给jsp
model.put("exception", loggedException);
... ...
}
相关的jsp代码
<c:if test="${exception != null}">
<spring:message message="${exception}" />
</c:if>
在Log中提供相关的国际化
一般来讲,我们没有必要在log中提供国际化,但在如果基于某些原因需要这样处理,我们该如何进行。
下面是一段小代码:
Exception exception = new Exception("testERROR",new Throwable("E . R . R . O . R"));
InternationalizedException i18nException = new InternationalizedException(exception,
"foo.test",
"This is a Error message",
"Test", "Error message");
logger.error(i18nException.toString());
log的输出为:
16:47:49.900 [http-nio-8080-exec-23] [ERROR] - cn.wei.chapter15.exception.InternationalizedException: This is a Error message
因为在InternationalizedException中,对应的构造函数如下,也就是设置了Exception的toString()为defaultMessage的值
public InternationalizedException(Throwable cause, String errorCode,String defaultMessage,Object ...objects ){
super(defaultMessage == null ? errorCode : defaultMessage, cause);
... ...
我们在InternationalizedException中增加下面的代码:
/* 【说明】我们需要将exception的输出内容进行国际化,可以重写Throwable接口的getLocalizedMessage()。 */
@Override
public String getLocalizedMessage(){
return this.errorCode;
}
输出为:
16:55:32.958 [http-nio-8080-exec-22] [ERROR] - cn.wei.chapter15.exception.InternationalizedException: foo.test
这表明getLocalizedMessage()影响了原来的toString(),但是有个问题,MessageSource是spring的bean,但InternationalizedException不在spring的框架中,因此不能inject。我们需要在这里创建一个MessageSource来处理,有点小麻烦,或者我们的这个国际化异常只为spring的组建服务,这样代码修订为:
private static final Locale DEFAULT_LOCALE = Locale.CHINA;
//我们这里沿用了getLocalizedMessage的名字,但不是override,不会影响toString()的输出,只是类似的名字表达相似的功能。
public String getLocalizedMessage(MessageSource messageSource){
return this.getLocalizedMessage(messageSource,this.getLocale());
}
public String getLocalizedMessage(MessageSource messageSource, Locale locale){
return messageSource.getMessage(this, locale);
}
protected final Locale getLocale(){
Locale locale = LocaleContextHolder.getLocale();
return locale == null ? InternationalizedException.DEFAULT_LOCALE : locale;
}
在某个spring 组建内,我们可以使用这国际化的log
@Inject private MessageSource messageSource;
... ...
logger.error(i18nException);
logger.error(i18nException.getLocalizedMessage(messageSource));
logger.error(i18nException.getLocalizedMessage(messageSource,Locale.US));
17:10:52.349 [http-nio-8080-exec-38] [ERROR] - cn.wei.chapter15.exception.InternationalizedException: This is a Error message
17:10:52.359 [http-nio-8080-exec-38] [ERROR] - 您好, Error message Test
17:10:52.369 [http-nio-8080-exec-38] [ERROR] - Hello, Test Error message
相关文章相关链接: 我的Professional Java for Web Applications相关文章