一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - 自己動手在Spring-Boot上加強國際化功能的示例

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

2021-04-23 11:47zzzzbw Java教程

這篇文章主要介紹了自己動手在Spring-Boot上加強國際化功能的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

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

本文主要實現(xiàn)的功能:

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

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

實現(xiàn)

國際化項目初始化

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

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

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

其中i18napplication.java設(shè)置了一個cookielocaleresolver,采用cookie來控制國際化的語言。還設(shè)置一個localechangeinterceptor攔截器來攔截國際化語言的變化。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@springbootapplication
@configuration
public 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");//設(shè)置存儲的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中寫了什么:

?
1
2
3
4
5
6
7
8
9
10
<!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>

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

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

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

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

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

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

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

繼承resourcebundlemessagesource

在項目下創(chuàng)建一個類繼承resourcebundlemessagesource或者reloadableresourcebundlemessagesource,起名為messageresourceextension。并且注入到bean中起名為messagesource,這里我們繼承resourcebundlemessagesource。

?
1
2
3
@component("messagesource")
public class messageresourceextension extends resourcebundlemessagesource {
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 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。

實現(xiàn)文件加載

現(xiàn)在我們可以開始在剛才創(chuàng)建的messageresourceextension

中寫加載文件的方法了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@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());
      }
    }
    //設(shè)置父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);
      }
 
    }
  }
 
  /**
   * 把普通文件名轉(zhuǎn)換成國際化文件名
   *
   * @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類被實例化之后自動調(diào)用init()方法。這個方法獲取到basefolder目錄下所有的國際化文件并設(shè)置到basenameset中。并且設(shè)置一個parentmessagesource,這會在找不到國際化信息的時候,調(diào)用父messagesource來查找國際化信息。
  2. getallbasenames()方法獲取到basefolder的路徑,然后調(diào)用getallfile()方法獲取到該目錄下所有的國際化文件的文件名。
  3. getallfile()遍歷目錄,如果是文件夾就繼續(xù)遍歷,如果是文件就調(diào)用geti18filename()把文件名轉(zhuǎn)為'i18n/basename/‘格式的國際化資源名。

所以簡單來說就是在messageresourceextension被實例化之后,把'i18n'文件夾下的資源文件的名字,加載到basenames中。現(xiàn)在來看一下效果。

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

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

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

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

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

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

?
1
2
3
4
5
6
7
8
...
<body>
<h1>國際化頁面!</h1>
<p th:text="#{hello}"></p>
<p th:text="#{merchant.hello}"></p>
<p th:text="#{dashboard.hello}"></p>
</body>
...

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

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

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

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

messageresourceextension重寫resolvecodewithoutarguments方法(如果有字符格式化的需求就重寫resolvecode方法)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@component("messagesource")
public class messageresourceextension extends resourcebundlemessagesource {
  ...
  public static string i18n_attribute = "i18n_attribute";
  
  @override
  protected string resolvecodewithoutarguments(string code, locale locale) {
    // 獲取request中設(shè)置的指定國際化文件名
    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'(等下再說這個在哪里設(shè)置),這個對應(yīng)我們想要顯示的國際化文件名,然后我們在basenameset中查找該文件,再通過getresourcebundle獲取到資源,最后再getstringornull獲取到對應(yīng)的國際化信息。

現(xiàn)在我們到我們的hellocontroller里加兩個方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@controller
public 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";
  }
}

看到我們在每個方法中都設(shè)置一個對應(yīng)的'i18n_attribute',這會在每次請求中設(shè)置對應(yīng)的國際化文件,然后在messageresourceextension中獲取。

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

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

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

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

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

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

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

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

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

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

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

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

雖然已經(jīng)可以指定對應(yīng)的國際化信息,但是這樣要在每個controller里的httpservletrequest中設(shè)置國際化文件實在太麻煩了,所以現(xiàn)在我們實現(xiàn)自動判定來顯示對應(yīng)的文件。

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

?
1
2
3
4
5
6
7
8
@target({elementtype.type, elementtype.method})
@retention(retentionpolicy.runtime)
public @interface i18n {
  /**
   * 國際化文件名
   */
  string value();
}

然后我們把這個創(chuàng)建的i18n 注解放在剛才的controller方法中,為了顯示他的效果,我們再創(chuàng)建一個shopcontrollerusercontroller,同時也創(chuàng)建對應(yīng)的'shop'和'user'的國際化文件,內(nèi)容也都是一個'hello'。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@controller
public 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";
  }
}
?
1
2
3
4
5
6
7
8
@i18n("shop")
@controller
public class shopcontroller {
  @getmapping("shop")
  public string shop() {
    return "shop";
  }
}
?
1
2
3
4
5
6
7
@controller
public class usercontroller {
  @getmapping("user")
  public string user() {
    return "user";
  }
}

我們把i18n注解分別放在hellocontroller下的dashboardmerchant方法下,和shopcontroller類上。并且去除了原來dashboardmerchant方法下設(shè)置‘i18n_attribute'的語句。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class messageresourceinterceptor implements handlerinterceptor {
  @override
  public void posthandle(httpservletrequest req, httpservletresponse rep, object handler, modelandview modelandview) {
 
    // 在方法中設(shè)置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;
    }
 
    // 根據(jù)controller名字設(shè)置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) {
    // 在跳轉(zhuǎn)到該方法先清除request中的國際化信息
    req.removeattribute(messageresourceextension.i18n_attribute);
    return true;
  }
}

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

首先,如果request中已經(jīng)有'i18n_attribute',說明在controller的方法中指定設(shè)置了,就不再判斷。

然后判斷一下進入攔截器的方法上有沒有i18n的注解,如果有就設(shè)置'i18n_attribute'到request中并退出攔截器,如果沒有就繼續(xù)。

再判斷進入攔截的類上有沒有i18n的注解,如果有就設(shè)置'i18n_attribute'到request中并退出攔截器,如果沒有就繼續(xù)。

最后假如方法和類上都沒有i18n的注解,那我們可以根據(jù)controller名自動設(shè)置指定的國際化文件,比如'usercontroller'那么就會去找'user'的國際化文件。

現(xiàn)在我們再運行一下看看效果,看到每個鏈接都顯示的他們對應(yīng)的國際化信息里的內(nèi)容。

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

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

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

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

最后

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

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

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

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

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

詳細的代碼可以看我github上spring-boot-i18n-pro的代碼

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

原文鏈接:https://segmentfault.com/a/1190000014538512

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本视频在线观看 | 亚洲精品国产一区二区第一页 | 亚洲成人免费观看 | 亚洲欧美久久一区二区 | 日韩一区二区在线视频 | 91色视| 国产亚洲精品看片在线观看 | 亚洲精品中文字幕第一区 | 日本孕妇大胆孕交 | 成人性色生活片免费网 | 久久精品国产视频澳门 | 欧美性一级交视频 | 亚洲第一二三四区 | meyd—447佐山爱在线 | 亚洲成人中文 | 精品午夜寂寞影院在线观看 | 精品乱lun小说 | 高清不卡一区 | 国产永久免费爽视频在线 | 免费高清在线视频色yeye | 欧美日韩国内 | 欧美搞逼视频 | 全色黄大色黄大片爽一次 | 美女张开下身让男人桶 | 蜜桃成熟3在线观看 | 亚洲天堂激情 | 扒开尿口 | 91制片厂果冻传媒杨柳作品 | 青青网在线视频 | 911亚洲精品国内自产 | 久久不射视频 | 婷婷在线观看香蕉五月天 | 男人操男人 | 操美女b| 咪咪爱小说 | yy111111免费观看 | 娇妻与公陈峰姚瑶小说在线阅读 | 欧美一级片在线视频 | 高h全肉np触手 | 成人综合婷婷国产精品久久免费 | 免费高清视频免费观看 |