清单 9. 编写 Thymeleaf 模板文件
:
:
<input type=“password” id=“password” name=“password”
class=“form-control” th:placeholder=“#{login_password}”>
<input type=“submit” name=“login-submit” id=“login-submit”
class=“form-control btn btn-info” th:value=“#{login_login}”>
经过一步步排查,原因是在配置文件 application.properties 错误配置了资源文件的路径如图 5 所示。正确的路径为 spring.messages.basename=i18n/login。
图 5. spring.messages.basename 路径不正确
Spring Boot 中时间日期格式化
====================
Java 8 提供了更便捷的日期时间 API 如 LocalDate、LocalTime 和 LocalDateTime,Spring Boot 架构中推荐使用 Java 8 中新的时间日期 API。LocalDate 较 Date 类的优点体现在以下几个方面:
-
Date 类打印出来的日期可读性差,通常需要使用 SimpleDateFormat 进行格式化,而 LocalDate 默认的格式为 YYYY-MM-DD。
-
LocalDate 较 Date 在日期计算及格式化方面更简单易用。
-
Date 类同时包括了日期和时间,而 LocalDate、LocalTime 和 LocalDateTime 分别表示日期、时间、日期和时间,使用起来非常灵活。
-
Date 类不支持多线程安全,LocalDate 等新的接口是线程安全的。
对于后台 Java 程序中的时间日期格式化问题,通常会重新定制化时间日期格式化接口,比如将 Locale 参数、想要显示的样式参数传入进去。清单 10 是格式化时间日期的接口示例代码,清单 11 是具体实现的示例代码,这里只列举了几种典型的格式化情况。
清单 10. 时间日期格式化接口
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Locale;
public interface I18nFormatter {
String formaFullDateTime(LocalDateTime date, Locale locale);
String formatFullDate(LocalDate originalDate, Locale locale);
String formatMediumDateTime(LocalDateTime date, Locale locale);
String formatMediumDate(LocalDate originalDate, Locale locale);
String formatMediumDateShortTime(LocalDateTime date, Locale locale);
String formatMediumTime(LocalTime originalDate, Locale locale);
String formatShortDateTime(LocalDateTime originalDate, Locale locale);
String formatShortDate(LocalDate originalDate, Locale locale);
String formatShortTime(LocalTime originalDate, Locale locale);
String formatShortDateMediumTime(LocalDateTime originalDate, Locale locale);
清单 11. 时间日期格式化实现
@Service(“I18nFormatter”)
public class I18nFormatterImpl implements I18nFormatter {
@Override
public String formatFullDate(LocalDate originalDate, Locale locale) {
DateTimeFormatter dateFormat =DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale);
return dateFormat.format(originalDate);
}
@Override
public String formatMediumDateTime(LocalDateTime date, Locale locale) {
DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM).withLocale(locale);
return dateFormat.format(date);
}
@Override
public String formatShortTime(LocalTime originalDate, Locale locale) {
DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(locale);
return dateFormat.format(originalDate);
}
}
在 Spring Boot 架构中接口与接口之间、前后端之间都使用 JSON 格式传输数据。对于日期格式的数据,如果采用默认方式不做处理,易读性和可用性不是很好。如清单 12 就是默认 JSON 格式的日期。请求得到的 JSON 格式日期可读性很差,如图 6 所示。
清单 12. 默认的时间日期
@RestController
public class LocalDateTimeController {
@GetMapping(“/time”)
public DateTime timeMapping() {
return new DateTime();
}
public class DateTime {
protected LocalDate localDate;
protected LocalDateTime localDateTime;
protected LocalTime localTime;
public DateTime() {
localDate = LocalDate.now();
localDateTime = LocalDateTime.now();
localTime = LocalTime.now();
}
public LocalDate getLocalDate() {
return localDate;
}
public LocalDateTime getLocalDateTime() {
return localDateTime;
}
public LocalTime getLocalTime() {
return localTime;
}
}
图 6. 默认格式返回的 JSON 格式日期
下面我们采用 @JsonFormat 序列化属性值,如清单 13 所示。
清单 13. 使用 @JsonFormat 进行标注
public class JsonDateTime extends DateTime {
@Override
@JsonFormat(pattern=“yyyy-MM-dd”)
public LocalDate getLocalDate() {
return super.localDate;
}
@Override
@JsonFormat(pattern=“yyyy-MM-dd HH:mm”)
public LocalDateTime getLocalDateTime() {
return super.localDateTime;
}
@Override
@JsonFormat(pattern=“HH:mm”)
public LocalTime getLocalTime() {
return localTime;
}
图 7. @JsonFormat 格式化后的 Json 格式日期
清单 13 我们只是对 JSON 格式的日期进行了格式化,但是还没有实现多语言化。我们采用清单 10 中定义的多语言格式化方法对 JSON 格式日期进行格式化,将日期参数定义为 String 类型。如清单 14 所示。
清单 14. 使用多语言格式化方法
@RestController
public class LocalDateTimeController{
@GetMapping(“/g11nDateTime”)
public G11nDateTime g11nDateTimeMapping(@RequestParam(value = “language”) String language) {
Locale locale = new Locale(“en”, “US”);
if(!StringUtils.isEmpty(language)){
String[] splits = language.split(“_”);
locale = new Locale(splits[0], splits[1]);
}
return new G11nDateTime(locale);
}
public class G11nDateTime {
protected String localDate;
protected String localDateTime;
protected String localTime;
public G11nDateTime(Locale locale) {
I18nFormatterImpl formatter = new I18nFormatterImpl();
localDate = formatter.formatFullDate(LocalDate.now(), locale);
localDateTime = formatter.formatMediumDateShortTime(LocalDateTime.now(), locale);
localTime = formatter.formatShortTime(LocalTime.now(), locale);
}
public String getLocalDate() {
return localDate;
}
public String getLocalDateTime() {
return localDateTime;
}
public String getLocalTime() {
return localTime;
}
}
当传入的语言参数为 zh_CN 时,响应的日期如图 8 所示。
图 8. 多语言的 JSON 格式日期
Spring Boot RESTful API 多语言支持
=============================
随着 Spring Boot 的快速发展,基于 RESTful
准的微服务接口应用也越来越广泛,RESTful API 使用 HTTP 协议来表示创建、检索、更新和删除 (CRUD) 操作。下面主要介绍在 Spring Boot 框架下,如何实现服务器端 RESTful API 的多语言支持,主要涉及到返回的内容和消息。通常有以下几方面需要考虑,如图 9 所示。
图 9. RESTful API 多语言支持
第一步: 封装和自定义资源文件读取工具
现行的开发框架大部分都会提供接口读取资源文件。涉及到资源文件读取时,ResourceBundle 是一个机制,主要用来根据不同的用户区域信息自动地读取对应的资源文件,ResourceBundle 是 Java 中的资源文件读取接口,图 10 总结了 Java 程序中 ResourceBundle 的管理机制。
图 10. Java 程序 Resource Bundle 管理流程
MessageSource 被 HierarchicalMessageSource 和 ApplicationContext 两个接口继承,如图 11 所示。
图 11. MessageSource 类图
ResourceBundleMessageSource 和 ReloadableResourceBundleMessageSource 是在 Java ResourceBundle 基础上 HierarchicalMessageSource 的两个具体实现类。他们与 ResourceBundle 的区别是不需要分别加载不同语言的资源文件,通过资源文件名(baseName)就可以加载整套的国际化资源文件;同时不再需要显示的调用 MessageFormat 方法,使用起来更加简单便捷;还可以读取 XML 格式的资源文件。
ReloadableResourceBundleMessageSource 可以定时刷新资源文件,以便在应用程序不重启的情况下感知资源文件的更新,还可以设置读取资源文件的编码方式。cacheSeconds 属性让 ReloadableResourceBundleMessageSource 根据设定的时间刷新监测资源文件是否有更新,但是刷新周期不能太短,否则影响应用程序的性能。如果 cacheSeconds 设置 -1,表示永远不刷新,这个时候 ReloadableResourceBundleMessageSource 和 ResourceBundleMessageSource 功能等同。
清单 15 在
ReloadableResourceBundleMessageSource 类的基础上,自定义资源文件读取接口,读取自定路径的资源文件。
清单 15. 自定义资源文件读取接口
public class CustomizeMessageResource {
private final static Logger logger = LoggerFactory.getLogger(CustomizeMessageResource.class);
private static MessageSourceAccessor accessor;
private static final String PATH_PARENT = “classpath:i18n/”;
private static final String SUFFIX = “.properties”;
public CustomizeMessageResource() {}
private void initMessageSourceAccessor() throws IOException{
logger.info(“init initMessageSourceAccessor…”);
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource resource = resourcePatternResolver.getResource(PATH_PARENT + “message” + SUFFIX);
String fileName = resource.getURL().toString();
int lastIndex = fileName.lastIndexOf(“.”);
String baseName = fileName.substring(0,lastIndex);
ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource = new ReloadableResourceBundleMessageSource();
reloadableResourceBundleMessageSource.setBasename(baseName);
reloadableResourceBundleMessageSource.setCacheSeconds(1800);
reloadableResourceBundleMessageSource.setDefaultEncoding(“UTF-8”);
accessor = new MessageSourceAccessor(reloadableResourceBundleMessageSource);
}
public String getMessage(String key, String lang) throws IOException {
initMessageSourceAccessor();
Locale locale = new Locale(“en”, “US”);
if (!lang.isEmpty()) {
locale = new Locale(lang.split(““)[0], lang.split(””)[1]);
}
return accessor.getMessage(key, null, “No such Property key”, locale);
}
public String getMessage(String key, String lang, Object… parameters) throws IOException {
initMessageSourceAccessor();
Locale locale = new Locale(“en”, “US”);
if (!lang.isEmpty()) {
locale = new Locale(lang.split(““)[0], lang.split(””)[1]);
}
return accessor.getMessage(key, parameters, “No such Property key”, locale);
}
** 第二步: 设计多语言的 API 返回状态码**
RESTful API 都会有返回状态码,为了支持多语言,在设计返回状态码接口的时候也需要考虑多语言的支持,下面以上传文件为例来说明如何设计反馈状态码以及返回接口的封装。在设计返回状态码的时候,涉及到显示的消息内容,这里用资源文件里的 Property Key 来表示,这样在返回状态封装类中比较容易动态地读取不同语言的返回消息,如清单 16 所示。
清单 16. 多语言 API 返回状态码
public enum G11nUploadCode {
OK(200, “OK”, “api_upload_response_ok”),
ERROR(400, “WRONG REQUEST”, “api_upload_response_wrong_request”),
CREATED(201, “CREATED”, “api_upload_response_create”),
UNAUTHORIZED(401, “UNAUTHORIZED”, “api_upload_response_unauthorized”),
FORBIDDEN(403, “FORBIDDEN”, “api_upload_response_forbidden”),
NOT_FOUND(404, “NOT FOUND”, “api_upload_response_not_found”);
private int code;
private String status;
private String propertyKey;
private G11nUploadCode(int code, String status, String propertyKey) {
this.code = code;
this.status= status;
this.propertyKey = propertyKey;
}
public void seCode(int code) {
this.code = code;
}
public int getCode() {
return this.code;
}
public String getStatus() {
return this.status;
}
public void seStatus(String status) {
this.status = status;
}
public void setPropertyKey(String propertyKey) {
this.propertyKey = propertyKey;
}
public String getPropertyKey() {
return this.propertyKey;
}
}
第三步: 多语言 API 返回接口封装
利用第一步中自定义的资源文件读取工具,动态的读取多余的返回信息,如清单 17 所示。
清单 17. 多语言 API 返回状态码
Spring 定义了访问资源文件的 MessageSource 接口,该接口有几个重要的方法用来读取资源文件,如表 1 所示。
表 1. MessageSource 接口说明
public class G11nUploadResult implements Serializable {
private static final long serialVersionUID = 1L;
private int code;
private String status;
private Object data;
public void setCode(int code) {
this.code = code;
}
public int getCode() {
return this.code;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return this.status;
}
public void setData(Object data) {
this.data = data;
}
public Object getData() {
return this.data;
}
public G11nUploadResult() {}
public G11nUploadResult(int code, String status, Object data) {
this.code = code;
this.status = status;
this.data = data;
}
public G11nUploadResult(G11nUploadCode responseCodeI18n, String language) throws IOException{
CustomizeMessageResource customizeMessageResource = new CustomizeMessageResource();
this.code = responseCodeI18n.getCode();
this.status = responseCodeI18n.getStatus();
System.out.println("Status: " + this.status);
this.data = customizeMessageResource.getMessage(responseCodeI18n.getPropertyKey(), language);
}
}
第四步: 在控制器中调用多语言的返回码
本步操作如清单 18 所示。
清单 18. 控制器中调用多语言返回码
@RestController
@Api(value=“uploadFiles”)
public class UploadFilesController {
private final Logger logger = LoggerFactory.getLogger(UploadFilesController.class);
private static String UPLOADED_FOLDER = “/users/tester/upload/”;
@PostMapping(“/uploadfiles”)
public G11nUploadResult uploadFile(@RequestParam(“file”) MultipartFile uploadfile) throws IOException {
logger.debug(“Single file uploa!”);
G11nUploadResult result = new G11nUploadResult();
CustomizeMessageResource customizeMessageResource = new CustomizeMessageResource();
if (uploadfile.isEmpty()) {
return new G11nUploadResult(G11nUploadCode.ERROR, “zh_CN”);
}
try {
saveUploadedFiles(Arrays.asList(uploadfile));
} catch (IOException e) {
return new G11nUploadResult(G11nUploadCode.NOT_FOUND, “zh_CN”);
}
logger.info("Successfully uploaded - " + uploadfile.getOriginalFilename());
result.setStatus(“OK”);
result.setData(customizeMessageResource.getMessage(“success_upload”, “zh_CN”, uploadfile.getOriginalFilename()));
return result;
}
}
图 12 是测试上传文件的结果,在调用 API 的时候,传递的参数是简体中文 (zh_CN),返回的状态和信息显示为中文信息,使用开发者熟悉的语言显示返回信息便于读取查看。
图 12. MessageSource 类图
结束语
===
本文总结了在 Spring Boot 框架下,如何开发一个多语言的应用程序及 RESTful API。阐述了 Spring Boot 架构下区域模型的原理,基于已有的区域模型定制化应用程序自己的区域模型;Thymeleaf 模板引擎对多语言的支持;Spring Boot 中时间日期格式化;以及 Spring Boot RESTful API 多语言支持实践。希望这篇文章能为正在开发国际化应用程序和微服务的您提供一定的参考。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了
1、JAVA面试核心知识整理(PDF):包含JVM,JAVA集合,JAVA多线程并发,JAVA基础,Spring原理,微服务,Netty与RPC,网络,日志,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
2、Redis学习笔记及学习思维脑图
3、数据面试必备20题+数据库性能优化的21个最佳实践
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
633703822)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了
[外链图片转存中…(img-pRbrWnfY-1713633703822)]
1、JAVA面试核心知识整理(PDF):包含JVM,JAVA集合,JAVA多线程并发,JAVA基础,Spring原理,微服务,Netty与RPC,网络,日志,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
[外链图片转存中…(img-LCy9aYAm-1713633703823)]
2、Redis学习笔记及学习思维脑图
[外链图片转存中…(img-KoUSlCH3-1713633703823)]
3、数据面试必备20题+数据库性能优化的21个最佳实践
[外链图片转存中…(img-JOCKOT6y-1713633703823)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!