Java for Web学习笔记(七五):国际化i18n(3)异常显示的国际化

本文介绍了一种自定义异常类实现国际化的方法,通过实现MessageSourceResolvable接口,使异常信息能够根据不同地区的用户显示相应的语言版本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题和解决方法

App因某种原因会抛出异常,我们会记录在log中,同时也要告知用户出了问题,一些错误信息需要显示给用户,这就有国际化的需求。我们不应简单将原始的Exception直接显示给用户:

  1. 异常可能是在底层,例如仓库读写数据出现异常,中间经过service,controller,才到view。在每个环节中,如何知道异常已经被记录下来,无需再次log? 可以自定义异常,如LoggedException,记录原始的log,然后抛出LoggedException,如果捕获的是LoggedException,我们不需要再次记录。LoggedException中可以将原始的异常作为cause。
  2. 用户很难明白,另外可能会向黑客泄漏某些可以利用的信息,需要向用户显示有效的说明,并实现国际化。 自定义异常,通过实现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相关文章
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值