springboot國際化與@valid國際化支持


springboot國際化

springboot對國際化的支持還是很好的,要實現國際化還簡單。主要流程是通過配置springboot的LocaleResolver解析器,當請求打到springboot的時候對請求的所需要的語言進解析,並保存在LocaleContextHolder中。之后就是根據當前的locale獲取message。
springboot中關於國際化消息處理的頂層接口是MessageSource,它有兩個開箱即可用的實現

1。新建國際化文件
這里只是中英文,右鍵可加入其它的語種
多語種情況下,不用打開每個語種的文件一個一個去修改。直接在message.properties編輯即可
messages.properties
9527=bojack
come=來吧
hello=你好
testVO.flag.Max=tai大了呀
testVO.flag.Min=tai小了
messages_en_US.properties
9527=jack
come=come
hello=hello
testVO.flag.Max=too big
testVO.flag.Min=too small
messages_zh_CN.properties
9527=傑克
come=來吧
hello=你好
testVO.flag.Max=太多了呀
testVO.flag.Min=太小了呀
2。配置國際化文件的位置
application.yml
spring:
  messages:
    basename: i18n/messages # 多個文件用逗號分隔
3。配置localeResolver,解析當前請求的locale,LocaleResolver是個接口,它也有多種實現,這個也可以根據自己的實際情況自已去實現,我這里用的是默認的解析器 AcceptHeaderLocaleResolver,他是通過獲取請求頭accept-language來獲取當前的locale。
    /**
     * 設置默認語言
     * @return
     */
    @Bean
    public LocaleResolver localeResolver() {
        AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
        acceptHeaderLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return  acceptHeaderLocaleResolver;
    }
注意:這里是zh-CN,如果寫成zh_CN是解析不了的。 這里還可以配置權重,具體參考https://cloud.tencent.com/developer/section/1189889
test.http
### 簡體中文
GET localhost:8080/i18n/test
Accept-Language: zh-CN

### 美國英語
GET localhost:8080/i18n/test
Accept-Language: en-US
4。如何使用
package com.springmvc.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

@RestController
@RequestMapping("i18n")
public class I18nController {

    @Autowired
    private MessageSource messageSource;

    @GetMapping("test")
    public Object test() {
        Locale locale = LocaleContextHolder.getLocale();
        String displayName = locale.getDisplayName();

        System.out.println("displayName = " + displayName);
        String hello = messageSource.getMessage("hello", null, locale);
        System.out.println("hello = " + hello);

        Map<String,Object> map = new HashMap<>();
        map.put("hello", hello);
        map.put("displayName", displayName);
        map.put("locale", locale);

        String come= messageSource.getMessage("come", null, locale);
        map.put("come", come);
        String c9527= messageSource.getMessage("9527", null, locale);
        map.put("9527", c9527);
        return map ;
    }
}

@valid 參數校驗與國際化

@valid默認其實是支持國際化的,只是它感覺支持的不是很好,如果在 userId上加個@Notnull注解,當userId為空的時候,只會提示 `不能為空`,如果有多個@notnull注解,不會提示具體是哪個不能為空。這個倒還好,可以解決。但是一般給用戶的提示,不可能提示 `userId 不能為空` 而是要提示成 `賬號不能為空`。所以默認的validationMessage用起來還有點麻煩,還不如直接用在國際化文件里寫好的message
抽象類: org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator
參數校驗不通過時拋出 MethodArgumentNotValidException 異常
    /**
     * The name of the default message bundle.
     */
    public static final String DEFAULT_VALIDATION_MESSAGES = "org.hibernate.validator.ValidationMessages";
國際化文件:
實體類
package com.springmvc.demo.vo;

import lombok.Data;

import javax.validation.constraints.Max;//MethodArgumentNotValidException
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Data
public class TestVO {

    @Min(value = 3)//這里不用寫messages了,因為要支持國際化
    @Max(value = 10)
    private Integer flag;
    
    @NotNull
    private Integer age;
}
國際化文件中寫成這樣
testVO.flag.Max=too big
testVO.flag.Min=too small
注解名可以寫在前也可以寫在后面,可以在文件中配置,注意默認是寫在前面的
spring:
  messages:
    basename: i18n/messages
  mvc:
    message-codes-resolver-format: postfix_error_code
參數校驗不能過時會拋出 MethodArgumentNotValidException  異常,因些可以在全局異常處理器中,捕獲異常,根據locale獲取相應的message
String message = messageSource.getMessage(fieldError, LocaleContextHolder.getLocale());
package com.springmvc.demo.controller;

import com.baomidou.mybatisplus.extension.api.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandlerResolver {

    @Autowired
    MessageSource messageSource;

    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleBodyValidException(MethodArgumentNotValidException exception) {
        Map<String, String> errors = new HashMap<String, String>();
        //得到所有的屬性錯誤
        List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
        //將其組成鍵值對的形式存入map
        for (FieldError fieldError : fieldErrors) {
            String[] str= fieldError.getField().split("\\.");
            if(str.length>1){
                errors.put(str[1], fieldError.getDefaultMessage());
            }else {
                errors.put(fieldError.getField(), fieldError.getDefaultMessage());
            }

            String message = messageSource.getMessage(fieldError, LocaleContextHolder.getLocale());
            return R.failed(message);
        }
        log.error("參數綁定異常,ex = {}", errors);
        return R.failed("haha");
    }
}
測試:test.http
###
GET localhost:8080/i18n/test
Accept-Language: zh

###
GET localhost:8080/i18n/test
Accept-Language: en-US

###
POST localhost:8080/test
Content-Type: application/json

{
  "flag": 2,
  "age": 22
}

###
POST localhost:8080/test
Content-Type: application/json
Accept-Language: en-US

{
  "flag": 5
}
源碼解析
從請求頭accept-language中取locale
org.apache.catalina.connector.Request#parseLocales
    /**
     * Parse request locales.
     */
    protected void parseLocales() {

        localesParsed = true;

        // Store the accumulated languages that have been requested in
        // a local collection, sorted by the quality value (so we can
        // add Locales in descending order).  The values will be ArrayLists
        // containing the corresponding Locales to be added
        TreeMap<Double, ArrayList<Locale>> locales = new TreeMap<>();
        Enumeration<String> values = getHeaders("accept-language");
        while (values.hasMoreElements()) {
            String value = values.nextElement();
            parseLocalesHeader(value, locales);
        }
        // Process the quality values in highest->lowest order (due to
        // negating the Double value when creating the key)
        for (ArrayList<Locale> list : locales.values()) {
            for (Locale locale : list) {
                addLocale(locale);
            }
        }
    }
org.springframework.context.support.AbstractMessageSource#getMessage(org.springframework.context.MessageSourceResolvable, java.util.Locale)
    @Override
    public final String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
        String[] codes = resolvable.getCodes();
        if (codes != null) {
            for (String code : codes) {
                String message = getMessageInternal(code, resolvable.getArguments(), locale);
                if (message != null) {
                    return message;
                }
            }
        }
        String defaultMessage = getDefaultMessage(resolvable, locale);
        if (defaultMessage != null) {
            return defaultMessage;
        }
        throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : "", locale);
    }
org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator#resolveMessage
	private String resolveMessage(String message, Locale locale) {
		String resolvedMessage = message;

		ResourceBundle userResourceBundle = userResourceBundleLocator
				.getResourceBundle( locale );

		ResourceBundle constraintContributorResourceBundle = contributorResourceBundleLocator
				.getResourceBundle( locale );

		ResourceBundle defaultResourceBundle = defaultResourceBundleLocator
				.getResourceBundle( locale );

		String userBundleResolvedMessage;
		boolean evaluatedDefaultBundleOnce = false;
		do {
			// search the user bundle recursive (step 1.1)
			userBundleResolvedMessage = interpolateBundleMessage(
					resolvedMessage, userResourceBundle, locale, true
			);

			// search the constraint contributor bundle recursive (only if the user did not define a message)
			if ( !hasReplacementTakenPlace( userBundleResolvedMessage, resolvedMessage ) ) {
				userBundleResolvedMessage = interpolateBundleMessage(
						resolvedMessage, constraintContributorResourceBundle, locale, true
				);
			}

			// exit condition - we have at least tried to validate against the default bundle and there was no
			// further replacements
			if ( evaluatedDefaultBundleOnce && !hasReplacementTakenPlace( userBundleResolvedMessage, resolvedMessage ) ) {
				break;
			}

			// search the default bundle non recursive (step 1.2)
			resolvedMessage = interpolateBundleMessage(
					userBundleResolvedMessage,
					defaultResourceBundle,
					locale,
					false
			);
			evaluatedDefaultBundleOnce = true;
		} while ( true );

		return resolvedMessage;
	}






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM