Spring Boot Restfutl 国际化

Spring Boot Restfull 国际化

一、思路

基于Restful的spring boot应用,涉及国际化的部分主要包括
1.请求参数异常信息国际化,此处参数校验采用hibernate validator,利用hibernate自身国际化机制。
2. 用户业务异常信息国际化,解决方法将业务异常进行封装,指定业务异常编码(对应国际化.prppreties的key),通过全局异常处理机制(@RestControllerAdvice),解析出当前语系,并根据异常编码,拿到对应的国际化信息,然后进行封装,返回给前端。
3. 其它信息国际化,通过AOP对返回信息拦截。返回对象封装如下:

@Getter
@NoArgsConstructor
public class ApiResult implements Serializable{

	private static final long serialVersionUID = 1L;

	public static final Integer STATUS_SUCCESS = 0;

	public static final Integer STATUS_FAILED = -1;

	/** 状态 **/
	private Integer status;

	/** i18n信息 **/
	private String message;

	/** i18n message key,对每个请求,需要解析出语系,填充message信息 **/
	@NonNull
	private String code;

	/** 返回数据 **/
	private Object rows;
	
	public ApiResult(Integer status, @NonNull String code) {
		super();
		this.status = status;
		this.code = code;
	}

	public ApiResult(Integer status, @NonNull String code, Object rows) {
		super();
		this.status = status;
		this.code = code;
		this.rows = rows;
	}

	public static ApiResult success() {
		ApiResult apiResult = new ApiResult(STATUS_SUCCESS, I18nResourceCode.COMMON_INFO_SUCCESS.getCode());
		return apiResult;
	}

	public static ApiResult failed() {
		ApiResult apiResult = new ApiResult(STATUS_FAILED, I18nResourceCode.COMMON_INFO_FAIELD.getCode());
		return apiResult;
	}

	public static ApiResult internalError() {
		ApiResult apiResult = new ApiResult(STATUS_FAILED, I18nResourceCode.COMMON_ERROR_INTERNAL.getCode());
		return apiResult;
	}

	public ApiResult setStatus(Integer status) {
		this.status = status;
		return this;
	}

	public ApiResult setMessage(String message) {
		this.message = message;
		return this;
	}

	public ApiResult setCode(String code) {
		this.code = code;
		return this;
	}

	public ApiResult setRows(Object rows) {
		this.rows = rows;
		return this;
	}

	@Setter
	@Getter
	@NoArgsConstructor
	public static class Page {

		private Integer pageSize;

		private Integer pageNumber;

		private Integer totalPage;

		private Integer totalRows;

		public Page(Integer pageSize, Integer pageNumber, Integer totalPage, Integer totalRows) {
			super();
			this.pageSize = pageSize;
			this.pageNumber = pageNumber;
			this.totalPage = totalPage;
			this.totalRows = totalRows;
		}

		public Page(Integer pageSize, Integer pageNumber, Integer totalPage) {
			super();
			this.pageSize = pageSize;
			this.pageNumber = pageNumber;
			this.totalPage = totalPage;
		}

	}

}

二、步骤

2.1 配置ResourceBundleMessageSource

@Configuration
public class ResourceConfig {

	@Bean
	@Primary
	ResourceBundleMessageSource messageSource() {
		ResourceBundleMessageSource bundleMessageSource = new ResourceBundleMessageSource();
		bundleMessageSource.setDefaultEncoding("UTF-8");
		// 指定国际化资源目录,其中i18n/error为文件夹,ValidationMessages为国际化文件前缀
		bundleMessageSource.setBasenames("i18n/error/ValidationMessages");
		bundleMessageSource.setCacheMillis(10);
		return bundleMessageSource;
	}
}

ResourceBundleMessageSource 读取资源属性文件(.properties),然后根据.properties文件的名称信息(本地化信息),匹配当前系统的国别语言信息,然后获取相应的properties文件的内容。

配置文件的命名格式一般为${name}_${language}_${region},此处指定了.properties的存储路径(i18n/error),baseName为ValidationMessages,也可以通过yml配置。

i18n国际化文件目录见下图

 

2.2 配置国际化文件解析组件

@Component
public class I18nComponent {

	private static final java.util.List<Locale> DEFAULT_ACCEPT_LOACLES = Arrays.asList(Locale.US, Locale.CHINESE);

	// 请求头信息,通过此解析locale
	public static final String HTTP_ACCEPT_LANGUAGE = "Accept-Language";

	@Autowired
	private ResourceBundleMessageSource bundleMessageSource;

	public Locale getLocale() {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes();
		HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
		String locale = httpServletRequest.getHeader(HTTP_ACCEPT_LANGUAGE);
		if (StringUtils.isEmpty(locale)) {
			return Locale.US;
		}
		return Locale.lookup(Locale.LanguageRange.parse(locale), DEFAULT_ACCEPT_LOACLES);
	}

	public String getLoacleMessage(String propertyKey) {
		Locale locale = getLocale();
		String message = bundleMessageSource.getMessage(propertyKey, null, locale);
		return message;

	}
	
	public String getLoacleMessageWithPlaceHolder(String propertyKey,Object ... params) {
		Locale locale = getLocale();
		String message = bundleMessageSource.getMessage(propertyKey, params, locale);
		return message;

	}

}

通过,此方法类,可以根据响应的键,拿到对应的国际化信息,其中public Locale getLocale(){}是解析当前设置语系,getLoacleMessage()和getLoacleMessageWithPlaceHolder()根据语系获取对应国际化信息。其中getLoacleMessageWithPlaceHolder()会将参数 param 替换到占位符,具体格式如下:

参考博客:https://blog.csdn.net/zhengyongcong/article/details/48666787

2.3 Hibernate validator配置

处理参数异常国际化,具体配置如下:

@Configuration
public class ValidatorConfig {

	@Resource
	private ResourceBundleMessageSource resourceBundleMessageSource;

	@Bean
	public Validator validator() throws Exception {
		LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
		validator.setValidationMessageSource(resourceBundleMessageSource);
		return validator;
	}

}

对于参数校验异常信息,直接利用hibernate 国际化机制,直接获取国际化信息,详见validaterExceptionHandler处理

@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {

	@Autowired
	private I18nComponent i18nComponent;

	@ExceptionHandler(BusinessException.class)
	public ApiResult businessExceptionHandler(final BusinessException businessException) {
		ApiResult apiResult = null;
		try {
			// 异常信息国际化
			apiResult = new ApiResult(ApiResult.STATUS_FAILED, businessException.getI18nResourceCode().getCode());
			apiResult.setMessage(i18nComponent.getLoacleMessage(businessException.getI18nResourceCode().getCode()));
		} catch (Exception e) {
			log.error("", e);
			apiResult = getLoacleInternalErrorApiResult();
		}
		return apiResult;

	}

	@ExceptionHandler(Exception.class)
	public ApiResult uncaughtExceptionHandler(Exception exception) {
		log.error("", exception);
		return getLoacleInternalErrorApiResult();
	}

	/**
	 * 参数校验异常
	 * 
	 * @param exception
	 * @return
	 */
	@ExceptionHandler(value = { BindException.class, MethodArgumentNotValidException.class,
			ConstraintViolationException.class })
	public ApiResult validaterExceptionHandler(final Exception exception) {
		ApiResult apiResult = null;

		try {
			String i18nMessage = null;
			apiResult = new ApiResult(ApiResult.STATUS_FAILED,
					I18nResourceCode.COMMON_ERROR_PARAMETER_VALIDATE.getCode());
			if (exception instanceof MethodArgumentNotValidException) {
				i18nMessage = methodArgumentNotValidExceptionHandler((MethodArgumentNotValidException) exception);
			} else if (exception instanceof ConstraintViolationException) {
				i18nMessage = methodConstraintViolationExceptionHandler((ConstraintViolationException) exception);
			} else if (exception instanceof BindException) {
				i18nMessage = methodBindExceptionHandler((BindException) exception);
			}
			apiResult.setMessage(i18nMessage);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			log.error("", e);
			apiResult = getLoacleInternalErrorApiResult();
			e.printStackTrace();
		}
		return apiResult;

	}

	private String methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException exception) {
		String i18nMessage = exception.getBindingResult().getFieldError().getDefaultMessage();
		return i18nMessage;
	}

	private String methodConstraintViolationExceptionHandler(ConstraintViolationException exception) {
		String i18nMessage = null;
		if (!CollectionUtils.isEmpty(exception.getConstraintViolations())) {
			Iterator<ConstraintViolation<?>> ite = exception.getConstraintViolations().iterator();
			i18nMessage = ite.hasNext() ? ite.next().getMessage() : exception.getLocalizedMessage();
		} else {
			i18nMessage = exception.getLocalizedMessage();
		}
		return i18nMessage;
	}

	private String methodBindExceptionHandler(BindException exception) {
		FieldError fieldError = exception.getFieldError();
		String i18nMessage = fieldError.getDefaultMessage();
		return i18nMessage;
	}

	private ApiResult getLoacleInternalErrorApiResult() {
		ApiResult apiResult = ApiResult.internalError();
		apiResult.setMessage(i18nComponent.getLoacleMessage(apiResult.getCode()));
		return apiResult;
	}

}

2.4 业务异常信息处理

 对也为异常信息进行封装,业务异常封装见下,异常处理逻辑见businessExceptionHandler()

@Getter
public class BusinessException extends RuntimeException {

	/* error code of i18n */
	@NonNull
	private I18nResourceCode i18nResourceCode;

	public BusinessException(@NonNull I18nResourceCode i18nResourceCode) {
		super();
		this.i18nResourceCode = i18nResourceCode;
	}

	public BusinessException(@NonNull I18nResourceCode i18nResourceCode, Throwable cause) {
		super(i18nResourceCode.getCode(), cause);
	}

}

其中I18nResourceCode为统一的国际化信息汇总类,定义如下:

@Getter
@AllArgsConstructor
public enum I18nResourceCode {

	COMMON_INFO_SUCCESS("common.info.success"),

	COMMON_INFO_FAIELD("common.info.failed"),
	
	COMMON_ERROR_INTERNAL("common.error.internal"),
	
	COMMON_ERROR_PARAMETER_VALIDATE("common.error.parameter.validate"),

	//business exception
	MODULE_USER_ERROR_EDIT_FAILED("module.user.error.edit.failed");
	
	
	
	private String code;
	
	//user parameter error code
	public static final String USER_ERROR_ID_IS_NOT_INT_RANGE = "{user.error.id.is.not.in.range}";
	
	public static final String USER_ERROR_NAME_MUST_NOT_BE_EMPTY = "{user.error.name.must.not.be.empty}";
	
	

}

2.5 其它业务非异常国际化信息处理

@ControllerAdvice(annotations = RestController.class)
public class I18nMessageAdvice implements ResponseBodyAdvice<Object> {

	@Autowired
	private I18nComponent i18nComponent;

	private static final Class[] DEFUALT_SUPPORT_ANNOTATIONS = { RequestMapping.class, GetMapping.class,
			PostMapping.class, DeleteMapping.class, PutMapping.class };

	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		// TODO Auto-generated method stub
		AnnotatedElement annotatedElement = returnType.getAnnotatedElement();
		return Arrays.stream(DEFUALT_SUPPORT_ANNOTATIONS)
				.anyMatch(annotaion -> annotaion.isAnnotation() && annotatedElement.isAnnotationPresent(annotaion));
	}

	/**
	 * 返回信息統一封裝為ApiResult,并进行国际化信息处理
	 */
	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
			ServerHttpResponse response) {
		// TODO Auto-generated method stub
		ApiResult apiResult = null;
		try {
			if (body == null) {
				apiResult = ApiResult.internalError();
			} else if (body instanceof ApiResult) {
				apiResult = (ApiResult) body;
			} else {
				// not support
				apiResult = ApiResult.internalError();
			}
			apiResult = apiResult.setMessage(i18nComponent.getLoacleMessage(apiResult.getCode()));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			apiResult = ApiResult.internalError();
			apiResult.setMessage(i18nComponent.getLoacleMessage(apiResult.getCode()));
		}

		return apiResult;
	}

}

详细源码可以参见我的GitHub地址https://github.com/dongzhi1129/spring-boot/tree/master/spring-boot-i18n

发布了15 篇原创文章 · 获赞 4 · 访问量 3258

猜你喜欢

转载自blog.csdn.net/dongzhi1129/article/details/90120877