亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

自己動手在Spring-Boot上加強國際化功能的示例

2024-07-14 08:40:14
字體:
來源:轉載
供稿:網友

前言

公司將項目由Struts2轉到Springmvc了,由于公司業務是境外服務,所以對國際化功能需求很高。Struts2自帶的國際化功能相對Springmvc來說更加完善,不過spring很大的特性就是可定定制化性強,所以在公司項目移植的到Springmvc的時候增加了其國際化的功能。特此整理記錄并且完善了一下。

本文主要實現的功能:

從文件夾中直接加載多個國際化文件后臺設置前端頁面顯示國際化信息的文件利用攔截器和注解自動設置前端頁面顯示國際化信息的文件

注:本文不詳細介紹怎么配置國際化,區域解析器等。

實現

國際化項目初始化

先創建一個基本的Spring-Boot+thymeleaf+國際化信息(message.properties)項目,如果有需要可以從我的Github下載。

簡單看一下項目的目錄和文件

SpringBoot,國際化,Spring,Boot國際化功能

其中I18nApplication.java設置了一個CookieLocaleResolver,采用cookie來控制國際化的語言。還設置一個LocaleChangeInterceptor攔截器來攔截國際化語言的變化。

@SpringBootApplication@Configurationpublic class I18nApplication {  public static void main(String[] args) {    SpringApplication.run(I18nApplication.class, args);  }  @Bean  public LocaleResolver localeResolver() {    CookieLocaleResolver slr = new CookieLocaleResolver();    slr.setCookieMaxAge(3600);    slr.setCookieName("Language");//設置存儲的Cookie的name為Language    return slr;  }  @Bean  public WebMvcConfigurer webMvcConfigurer() {    return new WebMvcConfigurer() {      //攔截器      @Override      public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new LocaleChangeInterceptor()).addPathPatterns("/**");      }    };  }}

我們再看一下hello.html中寫了什么:

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"><head>  <title>Hello World!</title></head><body><h1 th:text="#{i18n_page}"></h1><h3 th:text="#{hello}"></h3></body></html>

現在啟動項目并且訪問http://localhost:9090/hello(我在application.properties)中設置了端口為9090。

SpringBoot,國際化,Spring,Boot國際化功能

由于瀏覽器默認的語言是中文,所以他默認會去messages_zh_CN.properties中找,如果沒有就會去messages.properties中找國際化詞。

然后我們在瀏覽器中輸入http://localhost:9090/hello?locale=en_US,語言就會切到英文。同樣的如果url后參數設置為locale=zh_CH,語言就會切到中文。

SpringBoot,國際化,Spring,Boot國際化功能

從文件夾中直接加載多個國際化文件

在我們hello.html頁面中,只有'i18n_page'和'hello'兩個國際化信息,然而在實際項目中肯定不會只有幾個國際化信息那么少,通常都是成千上百個的,那我們肯定不能把這么多的國際化信息都放在messages.properties一個文件中,通常都是把國際化信息分類存放在幾個文件中。但是當項目大了以后,這些國際化文件也會越來越多,這時候在application.properties文件中一個個的去配置這個文件也是不方便的,所以現在我們實現一個功能自動加載制定目錄下所有的國際化文件。

繼承ResourceBundleMessageSource

在項目下創建一個類繼承ResourceBundleMessageSource或者ReloadableResourceBundleMessageSource,起名為MessageResourceExtension。并且注入到bean中起名為messageSource,這里我們繼承ResourceBundleMessageSource。

@Component("messageSource")public class MessageResourceExtension extends ResourceBundleMessageSource {}

注意這里我們的Component名字必須為'messageSource',因為在初始化ApplicationContext的時候,會查找bean名為'messageSource'的bean。這個過程在AbstractApplicationContext.java中,我們看一下源代碼

/*** Initialize the MessageSource.* Use parent's if none defined in this context.*/protected void initMessageSource() {  ConfigurableListableBeanFactory beanFactory = getBeanFactory();  if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {    this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);  ...  }}...

在這個初始化MessageSource的方法中,beanFactory查找注入名為MESSAGE_SOURCE_BEAN_NAME(messageSource)的bean,如果沒有找到,就會在其父類中查找是否有該名的bean。

實現文件加載

現在我們可以開始在剛才創建的MessageResourceExtension

中寫加載文件的方法了。

@Component("messageSource")public class MessageResourceExtension extends ResourceBundleMessageSource {  private final static Logger logger = LoggerFactory.getLogger(MessageResourceExtension.class);  /**   * 指定的國際化文件目錄   */  @Value(value = "${spring.messages.baseFolder:i18n}")  private String baseFolder;  /**   * 父MessageSource指定的國際化文件   */  @Value(value = "${spring.messages.basename:message}")  private String basename;  @PostConstruct  public void init() {    logger.info("init MessageResourceExtension...");    if (!StringUtils.isEmpty(baseFolder)) {      try {        this.setBasenames(getAllBaseNames(baseFolder));      } catch (IOException e) {        logger.error(e.getMessage());      }    }    //設置父MessageSource        ResourceBundleMessageSource parent = new ResourceBundleMessageSource();    parent.setBasename(basename);    this.setParentMessageSource(parent);  }  /**   * 獲取文件夾下所有的國際化文件名   *   * @param folderName 文件名   * @return   * @throws IOException   */  private String[] getAllBaseNames(String folderName) throws IOException {    Resource resource = new ClassPathResource(folderName);    File file = resource.getFile();    List<String> baseNames = new ArrayList<>();    if (file.exists() && file.isDirectory()) {      this.getAllFile(baseNames, file, "");    } else {      logger.error("指定的baseFile不存在或者不是文件夾");    }    return baseNames.toArray(new String[baseNames.size()]);  }  /**   * 遍歷所有文件   *   * @param basenames   * @param folder   * @param path   */  private void getAllFile(List<String> basenames, File folder, String path) {    if (folder.isDirectory()) {      for (File file : folder.listFiles()) {        this.getAllFile(basenames, file, path + folder.getName() + File.separator);      }    } else {      String i18Name = this.getI18FileName(path + folder.getName());      if (!basenames.contains(i18Name)) {        basenames.add(i18Name);      }    }  }  /**   * 把普通文件名轉換成國際化文件名   *   * @param filename   * @return   */  private String getI18FileName(String filename) {    filename = filename.replace(".properties", "");    for (int i = 0; i < 2; i++) {      int index = filename.lastIndexOf("_");      if (index != -1) {        filename = filename.substring(0, index);      }    }    return filename;  }}

依次解釋一下幾個方法。

  1. init()方法上有一個@PostConstruct注解,這會在MessageResourceExtension類被實例化之后自動調用init()方法。這個方法獲取到baseFolder目錄下所有的國際化文件并設置到basenameSet中。并且設置一個ParentMessageSource,這會在找不到國際化信息的時候,調用父MessageSource來查找國際化信息。
  2. getAllBaseNames()方法獲取到baseFolder的路徑,然后調用getAllFile()方法獲取到該目錄下所有的國際化文件的文件名。
  3. getAllFile()遍歷目錄,如果是文件夾就繼續遍歷,如果是文件就調用getI18FileName()把文件名轉為'i18n/basename/‘格式的國際化資源名。

所以簡單來說就是在MessageResourceExtension被實例化之后,把'i18n'文件夾下的資源文件的名字,加載到Basenames中?,F在來看一下效果。

首先我們在application.properties文件中添加一個spring.messages.baseFolder=i18n,這會把'i18n'這個值賦值給MessageResourceExtension中的baseFolder

在啟動后看到控制臺里打印出了init信息,表示被@PostConstruct注解的init()方法已經執行。

SpringBoot,國際化,Spring,Boot國際化功能

然后我們再創建兩組國際化信息文件:'dashboard'和'merchant',里面分別只有一個國際化信息:'dashboard.hello'和'merchant.hello'。

SpringBoot,國際化,Spring,Boot國際化功能

之后再修改一下hello.html文件,然后訪問hello頁面。

...<body><h1>國際化頁面!</h1><p th:text="#{hello}"></p><p th:text="#{merchant.hello}"></p><p th:text="#{dashboard.hello}"></p></body>...

SpringBoot,國際化,Spring,Boot國際化功能SpringBoot,國際化,Spring,Boot國際化功能

可以看到網頁中加載了'message','dashboard'和'merchant'中的國際化信息,說明我們已經成功一次性加載了'i18n'文件夾下的文件。

后臺設置前端頁面顯示國際化信息的文件

s剛才那一節我們成功加載了多個國際化文件并顯示出了他們的國際化信息。但是'dashboard.properties'中的國際化信息為'dashboard.hello'而'merchant.properties'中的是'merchant.hello',這樣每個都要寫一個前綴豈不是很麻煩,現在我想要在'dashboard'和'merchant'的國際化文件中都只寫'hello'但是顯示的是'dashboard'或'merchant'中的國際化信息。

MessageResourceExtension重寫resolveCodeWithoutArguments方法(如果有字符格式化的需求就重寫resolveCode方法)。

@Component("messageSource")public class MessageResourceExtension extends ResourceBundleMessageSource {  ...  public static String I18N_ATTRIBUTE = "i18n_attribute";    @Override  protected String resolveCodeWithoutArguments(String code, Locale locale) {    // 獲取request中設置的指定國際化文件名    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();    final String i18File = (String) attr.getAttribute(I18N_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);    if (!StringUtils.isEmpty(i18File)) {      //獲取在basenameSet中匹配的國際化文件名      String basename = getBasenameSet().stream()          .filter(name -> StringUtils.endsWithIgnoreCase(name, i18File))          .findFirst().orElse(null);      if (!StringUtils.isEmpty(basename)) {        //得到指定的國際化文件資源        ResourceBundle bundle = getResourceBundle(basename, locale);        if (bundle != null) {          return getStringOrNull(bundle, code);        }      }    }    //如果指定i18文件夾中沒有該國際化字段,返回null會在ParentMessageSource中查找    return null;  }  ...}

在我們重寫的resolveCodeWithoutArguments方法中,從HttpServletRequest中獲取到‘I18N_ATTRIBUTE'(等下再說這個在哪里設置),這個對應我們想要顯示的國際化文件名,然后我們在BasenameSet中查找該文件,再通過getResourceBundle獲取到資源,最后再getStringOrNull獲取到對應的國際化信息。

現在我們到我們的HelloController里加兩個方法。

@Controllerpublic class HelloController {  @GetMapping("/hello")  public String index(HttpServletRequest request) {    request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "hello");    return "system/hello";  }  @GetMapping("/dashboard")  public String dashboard(HttpServletRequest request) {    request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "dashboard");    return "dashboard";  }  @GetMapping("/merchant")  public String merchant(HttpServletRequest request) {    request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "merchant");    return "merchant";  }}

看到我們在每個方法中都設置一個對應的'I18N_ATTRIBUTE',這會在每次請求中設置對應的國際化文件,然后在MessageResourceExtension中獲取。

這時我們看一下我們的國際化文件,我們可以看到所有關鍵字都是'hello',但是信息卻不同。

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

同時新增兩個html文件分別是'dashboard.html'和'merchant.html',里面只有一個'hello'的國際化信息和用于區分的標題。

<!-- 這是hello.html --><body><h1>國際化頁面!</h1><p th:text="#{hello}"></p></body>
<!-- 這是dashboard.html --><body><h1>國際化頁面(dashboard)!</h1><p th:text="#{hello}"></p></body>
<!-- 這是merchant.html --><body><h1>國際化頁面(merchant)!</h1><p th:text="#{hello}"></p></body>

這時我們啟動項目看一下。

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

可以看到雖然在每個頁面的國際化詞都是'hello',但是我們在對應的頁面顯示了我們想要顯示的信息。

利用攔截器和注解自動設置前端頁面顯示國際化信息的文件

雖然已經可以指定對應的國際化信息,但是這樣要在每個controller里的HttpServletRequest中設置國際化文件實在太麻煩了,所以現在我們實現自動判定來顯示對應的文件。

首先我們創建一個注解,這個注解可以放在類上或者方法上。

@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface I18n {  /**   * 國際化文件名   */  String value();}

然后我們把這個創建的I18n 注解放在剛才的Controller方法中,為了顯示他的效果,我們再創建一個ShopControllerUserController,同時也創建對應的'shop'和'user'的國際化文件,內容也都是一個'hello'。

@Controllerpublic class HelloController {  @GetMapping("/hello")  public String index() {    return "system/hello";  }  @I18n("dashboard")  @GetMapping("/dashboard")  public String dashboard() {    return "dashboard";  }  @I18n("merchant")  @GetMapping("/merchant")  public String merchant() {    return "merchant";  }}
@I18n("shop")@Controllerpublic class ShopController {  @GetMapping("shop")  public String shop() {    return "shop";  }}
@Controllerpublic class UserController {  @GetMapping("user")  public String user() {    return "user";  }}

我們把I18n注解分別放在HelloController下的dashboardmerchant方法下,和ShopController類上。并且去除了原來dashboardmerchant方法下設置‘I18N_ATTRIBUTE'的語句。

準備工作都做好了,現在看看如何實現根據這些注解自動的指定國際化文件。

public class MessageResourceInterceptor implements HandlerInterceptor {  @Override  public void postHandle(HttpServletRequest req, HttpServletResponse rep, Object handler, ModelAndView modelAndView) {    // 在方法中設置i18路徑    if (null != req.getAttribute(MessageResourceExtension.I18N_ATTRIBUTE)) {      return;    }    HandlerMethod method = (HandlerMethod) handler;    // 在method上注解了i18    I18n i18nMethod = method.getMethodAnnotation(I18n.class);    if (null != i18nMethod) {      req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, i18nMethod.value());      return;    }    // 在Controller上注解了i18    I18n i18nController = method.getBeanType().getAnnotation(I18n.class);    if (null != i18nController) {      req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, i18nController.value());      return;    }    // 根據Controller名字設置i18    String controller = method.getBeanType().getName();    int index = controller.lastIndexOf(".");    if (index != -1) {      controller = controller.substring(index + 1, controller.length());    }    index = controller.toUpperCase().indexOf("CONTROLLER");    if (index != -1) {      controller = controller.substring(0, index);    }    req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, controller);  }  @Override  public boolean preHandle(HttpServletRequest req, HttpServletResponse rep, Object handler) {    // 在跳轉到該方法先清除request中的國際化信息    req.removeAttribute(MessageResourceExtension.I18N_ATTRIBUTE);    return true;  }}

簡單講解一下這個攔截器。

首先,如果request中已經有'I18N_ATTRIBUTE',說明在Controller的方法中指定設置了,就不再判斷。

然后判斷一下進入攔截器的方法上有沒有I18n的注解,如果有就設置'I18N_ATTRIBUTE'到request中并退出攔截器,如果沒有就繼續。

再判斷進入攔截的類上有沒有I18n的注解,如果有就設置'I18N_ATTRIBUTE'到request中并退出攔截器,如果沒有就繼續。

最后假如方法和類上都沒有I18n的注解,那我們可以根據Controller名自動設置指定的國際化文件,比如'UserController'那么就會去找'user'的國際化文件。

現在我們再運行一下看看效果,看到每個鏈接都顯示的他們對應的國際化信息里的內容。

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

最后

剛才完成了我們整個國際化增強的基本功能,最后我把全部代碼整理了一下,并且整合了bootstrap4來展示了一下功能的實現效果。

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

SpringBoot,國際化,Spring,Boot國際化功能

詳細的代碼可以看我Github上Spring-Boot-I18n-Pro的代碼

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
色妞色视频一区二区三区四区| 色综合老司机第九色激情| 成人免费网视频| 91精品国产免费久久久久久| 日本一区二区三区在线播放| 国产婷婷成人久久av免费高清| 国产精品91久久久久久| 国产精品高潮在线| 国产精品中文久久久久久久| 色久欧美在线视频观看| 国产精品一久久香蕉国产线看观看| 亚洲最大福利网| 中日韩美女免费视频网站在线观看| 国产欧美在线视频| 91成人在线视频| 亚洲激情视频在线观看| 国产成人精品在线视频| 国产精品成人国产乱一区| 久久欧美在线电影| 中文字幕日韩专区| 亚洲精品小视频在线观看| 久久人人爽人人爽爽久久| 欧美中文字幕视频在线观看| 欧美裸体男粗大视频在线观看| 国产精品爽爽爽爽爽爽在线观看| 555www成人网| 欧美精品videos| 成人久久精品视频| 国产欧美日韩中文| 欧洲成人午夜免费大片| 精品人伦一区二区三区蜜桃网站| 久久久精品视频成人| 88国产精品欧美一区二区三区| 中文字幕av一区二区三区谷原希美| 久久免费精品视频| 一区二区三区无码高清视频| 亚洲一区二区三区在线视频| 九九久久综合网站| 亚洲精品国产精品久久清纯直播| 51精品国产黑色丝袜高跟鞋| 亚洲精品99久久久久| 国产欧美精品一区二区三区-老狼| 欧美体内谢she精2性欧美| 国内自拍欧美激情| 中文字幕亚洲一区二区三区| 国产精品日韩久久久久| 亚洲男人天堂2019| 青青草99啪国产免费| 久久精品久久久久| 欧美视频在线看| 亚洲欧美综合图区| 亚洲国产天堂久久综合网| 成人免费在线视频网址| 国产97免费视| 精品国产乱码久久久久久婷婷| 国产视频一区在线| 疯狂做受xxxx欧美肥白少妇| 亚洲片在线观看| 亚洲日本aⅴ片在线观看香蕉| 欧美精品国产精品日韩精品| 国产成人福利夜色影视| 国产精品v日韩精品| 国产精品免费视频久久久| 国产精彩精品视频| 欧美成aaa人片在线观看蜜臀| 日本午夜在线亚洲.国产| 欧美俄罗斯性视频| 不用播放器成人网| 亚洲国产精品嫩草影院久久| 欧美综合在线观看| 8050国产精品久久久久久| 欧美性极品xxxx做受| 亚洲欧洲高清在线| 国产精品一香蕉国产线看观看| 亚洲第一av网| 久久91超碰青草是什么| 亚洲欧美日韩中文视频| 亚洲一区国产精品| 国产欧美日韩精品在线观看| 亚洲a级在线观看| 亚洲视频电影图片偷拍一区| 国产精品在线看| 亚洲高清一区二| 亚洲va男人天堂| 久久天堂av综合合色| 欧美裸体xxxx极品少妇| 国产一区二区在线播放| 九九视频直播综合网| 亚洲男人的天堂网站| 国产激情综合五月久久| 欧美在线亚洲在线| 91深夜福利视频| 色av吧综合网| 欧美激情网站在线观看| 国产区精品视频| 日韩精品视频免费| 2018国产精品视频| 国产精品视频久| 亚洲第一男人av| 国产精品电影网| 国产精品精品国产| 97高清免费视频| 国产日韩av在线| 精品国产31久久久久久| 国产一区欧美二区三区| 欧美精品久久一区二区| 亚洲男人av电影| 欧美激情视频一区二区三区不卡| 国产精品久久久久一区二区| 精品久久久久久亚洲精品| 日本19禁啪啪免费观看www| 亚洲午夜av电影| 欧美日韩激情视频8区| 九色成人免费视频| 国产精品一区二区久久国产| 久久视频在线观看免费| 亚洲国产高清福利视频| 一区二区三区动漫| 成人妇女免费播放久久久| 亚洲一区二区在线播放| 国产精品中文久久久久久久| 尤物yw午夜国产精品视频| 中文字幕亚洲无线码a| 久久久噜噜噜久久中文字免| 亚洲第一色中文字幕| 欧美另类老女人| 久久成人综合视频| 亚洲国产欧美精品| 日韩在线免费高清视频| 91精品久久久久久久久久入口| 欧美国产亚洲精品久久久8v| 久久成年人免费电影| 欧美在线免费视频| 久久国产精品偷| 久久久久久久国产精品视频| 久久综合免费视频影院| 26uuu亚洲国产精品| 国产精品成人免费视频| 尤物九九久久国产精品的特点| 欧美福利视频在线| 国产精品普通话| 91热精品视频| 国产精品九九久久久久久久| 乱亲女秽乱长久久久| 亚洲精品网址在线观看| 亚洲免费视频一区二区| 欧美wwwxxxx| 国产91九色视频| 亚洲精品国产精品国自产在线| 欧美成人免费在线观看| 日韩国产高清视频在线| 国产成人av网址| 欧美精品在线免费观看| 岛国av在线不卡| 俺去啦;欧美日韩| 日韩欧美国产高清91| 亚洲一区av在线播放| 国产精品第七十二页| 久久久久久香蕉网| 成人a在线观看| 海角国产乱辈乱精品视频| 欧美在线视频播放| 亚洲人成网站777色婷婷| 成人黄色免费在线观看|