SpringBoot international configuration component supports local configuration and database configuration


Insert image description here

0. Preface

Wrote a native SpringBoot international configuration component to support local configuration and database configuration

Background: I recently spent time refactoring the international component Starter used in the project to make it easier to use. Basically, it supports reading from local configuration and database configuration, and supports the internationalization needs of mobile terminals such as web terminals and small programs.

i18n-spring-boot-starter

1. How to use

Spring Boot international components

0.Introduce dependencies

After the code is packaged locally,
it can be introduced into projects that require internationalization.


<dependency>
    <groupId>com.bdkjzx.project</groupId>
    <artifactId>i18n-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

1. Configuration items


#添加国际化
spring.ex.i18n.enable=true
# 如果未翻译是否将code 初始化入库
spring.ex.i18n.mark=false
spring.ex.i18n.default-locale=zh-CN
spring.ex.i18n.data-source=primary
spring.ex.i18n.config-table=config_i18n_message

2. Initialize the internationalization configuration table


CREATE TABLE `config_i18n_message` (
  `code` varchar(128)   NOT NULL,
  `zh-CN` varchar(128)  DEFAULT NULL,
  `zh-TW` varchar(128)  DEFAULT NULL,
  `en-US` varchar(1024)   DEFAULT NULL COMMENT '英文',
  PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='国际化配置表'


If you configure it locally, use the native configuration method. The disadvantage is that it needs to be updated manually, and each service needs to be configured. It is recommended to use database table configuration
messages_zh_CN.properties , messages_en_US.properties

3.How to use

I.n("操作成功")

Or on the returned unified result object, the following is an example, you need to add it to the unified response of your project

public class ApiResponse<T> {
    
    
    private int code;
    private String message;
    private T data;
    private ErrorDetails error;

    public ApiResponse() {
    
    
    }
    /**
     * message给消息进行国际化包装
     * @param message
     */
    public ApiResponse(int code, String message, T data, ErrorDetails error) {
    
    
        this.code = code;
        this.message = I.n(message);
        this.data = data;
        this.error = error;
    }

    // Getter and Setter methods

    public int getCode() {
    
    
        return code;
    }

    public void setCode(int code) {
    
    
        this.code = code;
    }

    public String getMessage() {
    
    
        return message;
    }

    /**
     * 给消息进行国际化包装
     * @param message
     */
    public void setMessage(String message) {
    
    
        this.message = I.n(message);
    }

    public T getData() {
    
    
        return data;
    }

    public void setData(T data) {
    
    
        this.data = data;
    }

    public ErrorDetails getError() {
    
    
        return error;
    }

    public void setError(ErrorDetails error) {
    
    
        this.error = error;
    }
}

5. Please see the entrance for expansion

  com.bdkjzx.project.i18n.config.I18nAutoConfig

2. Core source code

package com.bdkjzx.project.i18n.config;

import com.bdkjzx.project.i18n.I18nHolder;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "spring.ex.i18n")
@Setter
@Getter
public class I18nProperties {
    
    

    /**
     * 是否启用国际化功能:<br>
     *     - 启用:会创建和国际化相关的数据源、缓存等等;<br>
     *     - 不启用:{@link I18nHolder} 可以正常使用,返回原值,不会创建国际化相关的各种Bean<br>
     *
     *  默认:不启用,需要手动开启
     */
    private Boolean enable = false;

    /**
     * 国际化数据表所在的数据源,入需指定,则写入数据源名称。<br>
     *     此配置的作用是允许多个服务通过共享一个i18n配置表,从而共用一套i18n翻译。<br>
     *     默认为空,表示使用primary数据源。
     */
    private String dataSource = "primary";

    /**
     * 默认地区(语言)
     */
    private String defaultLocale = "zh_CN";

    /**
     * 查询i18n配置表的名称,用于自定义修改表。<br>
     *     默认:config_i18n_message
     */
    private String configTable = "config_i18n_message";

    /**
     * i18n配置表的字段名。根据i18n配置表决定此配置<br>
     *  默认:code
     */
    private String configCodeColumn = "code";

    /**
     * i18n缓存更新时间(小时数),会提供手工刷新缓存的入口,所以不必频繁刷新<br>
     *     默认值为-1,表示长期有效。<br>
     */
    private Integer cacheHours = -1;

    /**
     * 当未找到i18n的code时,是否将其记录到表中,以便统一处理<br>
     *     默认:关闭
     */
    private Boolean mark = false;

    /**
     * 用于记录无效code的线程池缓冲区大小
     */
    private Integer markPoolSize = 2000;

    /**
     * 是否在 {@link com.bdkjzx.project.i18n.repository.I18nMessageResource} 未找到配置时,再使用Spring默认方案,
     *     从本地加载国际化资源。
     *  默认:关闭
     */
    private Boolean useLocale = false;


}

package com.bdkjzx.project.i18n.config;


import com.bdkjzx.project.i18n.I18nHolder;
import com.bdkjzx.project.i18n.filter.I18nFilter;


import com.bdkjzx.project.i18n.repository.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.context.MessageSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import com.bdkjzx.project.i18n.interceptor.I18nInterceptor;

import javax.sql.DataSource;
import java.util.Locale;
import java.util.concurrent.Executor;


@Configuration
@EnableConfigurationProperties({
    
    I18nProperties.class})
@Slf4j
public class I18nAutoConfig {
    
    
    @Bean
    public I18nHolder getI18nUtil(@Autowired(required = false) I18nMessageResource messageSource,
                                  @Autowired(required = false) I18nLocaleHolder i18nLocaleHolder,
                                  @Autowired I18nProperties i18NProperties) {
    
    
        // 不论是否启用都会配置,保证这个工具类不会报错
        return i18NProperties.getEnable() ? new I18nHolder(messageSource, i18nLocaleHolder) : new I18nHolder();
    }

    @ConditionalOnProperty(prefix = "spring.ex.i18n", name = "enable", havingValue = "true")
    @Configuration
    static class I18nFilterConfig {
    
    

        @Autowired
        private I18nLocaleHolder i18nLocaleHolder;

        @Bean
        public I18nFilter i18nFilter() {
    
    
            I18nFilter i18nFilter = new I18nFilter();

            I18nInterceptor interceptor = new I18nInterceptor();
            interceptor.setI18nLocaleHolder(i18nLocaleHolder);
            i18nFilter.setI18nInterceptor(interceptor);
            return i18nFilter;
        }
    }

    @ConditionalOnProperty(prefix = "spring.ex.i18n", name = "enable", havingValue = "true")
    @Configuration
    @EnableCaching
    @ComponentScan("com.bdkjzx.project.i18n")
    static class I18nResourceConfig {
    
    

        /**
         * 采用默认的配置文件配置 messages开头的文件,编码为utf8<br>
         * 如 messages_zh_CN.properties ,  messages_en_US.properties
         *
         * @return {@link MessageSourceProperties}
         */
        @Bean
        public MessageSourceProperties messageSourceProperties() {
    
    
            return new MessageSourceProperties();
        }

        @Bean
        public ResourceBundleMessageSource initResourceBundleMessageSource(MessageSourceProperties messageSourceProperties) {
    
    
            ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
            resourceBundleMessageSource.setBasename(messageSourceProperties.getBasename());
            resourceBundleMessageSource.setDefaultEncoding(messageSourceProperties.getEncoding().name());
            return resourceBundleMessageSource;
        }

        @Bean
        @Autowired
        public I18nMessageResource initMessageResource(ResourceBundleMessageSource resourceBundleMessageSource,
                                                       I18nLocaleHolder i18NLocaleSettings) {
    
    
            I18nMessageResource i18nMessageResource = new I18nMessageResource(i18NLocaleSettings.getDefaultLocale());
            i18nMessageResource.setParentMessageSource(resourceBundleMessageSource);
            return i18nMessageResource;
        }

        @Bean
        @Autowired
        public I18nLocaleHolder getI18nLocaleSetting(I18nProperties i18nProperties) {
    
    
            Locale locale;
            try {
    
    
                locale = new Locale.Builder()
                        .setLanguageTag(i18nProperties.getDefaultLocale().replace("_", "-").toLowerCase())
                        .build();
            } catch (Exception e) {
    
    
                log.error(String.format("解析默认语言时出现错误, setting = %s", i18nProperties.getDefaultLocale()), e);
                throw new IllegalArgumentException("解析默认语言时出现错误,请查看日志");
            }
            return new I18nLocaleHolder(locale);
        }

        @Bean(name = "i18nJdbcTemplate")
        @ConditionalOnMissingBean(name = "i18nJdbcTemplate")
        public JdbcTemplate getJdbcTemplate(@Autowired(required = false) @Qualifier("i18nDataSource") DataSource i18nDataSource) {
    
    
            try {
    
    
                if (i18nDataSource == null) {
    
    
                    log.error("未配置国家化数据源,请使用@Bean构造一个名为i18nDataSource的DataSource或者直接重新此方法");
                }
                return new JdbcTemplate(i18nDataSource);
            } catch (BeansException e) {
    
    
                log.error("无效的数据源{}", i18nDataSource, e);
                throw new IllegalArgumentException("创建数据源时出现错误,请查看日志");
            }
        }

        @Autowired
        @Bean(name = "defaultI18nDataLoadService")
        public I18nConfigDbLoader getI18nDataLoadService(I18nProperties i18nProperties,
                                                         @Qualifier("i18nJdbcTemplate") JdbcTemplate jdbcTemplate) {
    
    
            return new SimpleI18NConfigDbLoaderImpl(i18nProperties.getConfigCodeColumn(),
                    i18nProperties.getConfigTable(), jdbcTemplate);
        }

        @Autowired
        @Bean(name = "i18nCacheManager")
        public CacheManager getCacheManager(I18nProperties i18nProperties) {
    
    
            CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
            if (i18nProperties.getCacheHours() > 0) {
    
    
                // 缓存创建后,经过固定时间(小时),更新
                caffeineCacheManager.setCacheSpecification(String.format("refreshAfterWrite=%sH", i18nProperties.getCacheHours()));
            }
            return caffeineCacheManager;
        }

        /**
         * 线程池配置
         */
        @ConditionalOnProperty(prefix = "spring.ex.i18n", name = "mark", havingValue = "true")
        @Configuration
        @EnableAsync
        static class I18nInvalidMarkerConfig {
    
    

            @Bean("i18nExecutor")
            @Autowired
            public Executor getAsyncExecutor(I18nProperties i18NProperties) {
    
    
                ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
                executor.setCorePoolSize(0);
                executor.setMaxPoolSize(2);
                executor.setQueueCapacity(i18NProperties.getMarkPoolSize());
                executor.setThreadNamePrefix("i18n-executor-");
                executor.initialize();
                return executor;
            }

            @Bean
            public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
                return new SimpleAsyncUncaughtExceptionHandler();
            }

        }

    }

}

Implement an interceptor I18nInterceptor

Its function is to implement an interceptor for internationalization (i18n) functions. Used to handle the internationalization of web applications, that is, display corresponding internationalized resource files according to the user's language settings.

  1. Get the language setting from the request cookie or header.
  2. Store the language setting into the i18nLocaleHolder for use in subsequent request processing.
  3. After request processing is complete, clear the language setting in i18nLocaleHolder.
package com.bdkjzx.project.i18n.interceptor;

import com.bdkjzx.project.i18n.repository.I18nLocaleHolder;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 国际化拦截器,用于处理Web应用的国际化(i18n)。
 */
@Slf4j
public class I18nInterceptor implements HandlerInterceptor {
    
    

    private I18nLocaleHolder i18nLocaleHolder;

    private final Map<String, Locale> localeMap = new HashMap<>(8);

    private static final String NAME_OF_LANGUAGE_SETTING = "lang";

    /**
     * 在实际处理程序方法调用之前执行的预处理方法。
     * 从请求的cookie或header中获取语言设置,并将其设置到i18nLocaleHolder中。
     * 如果语言设置为空或无效,则返回true以允许请求继续进行。
     */
    @Override
    public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
    
    
        String lang = getLangFromCookies(request);
        if (StringUtils.isEmpty(lang)) {
    
    
            lang = getLangFromHeader(request);
        }

        if (StringUtils.isEmpty(lang)) {
    
    
            return true;
        }
        try {
    
    
            i18nLocaleHolder.setThreadLocale(getLocaleByLang(lang));
        } catch (Exception e) {
    
    
            log.error("无效的语言设置:{}", lang, e);
        }

        return true;
    }

    /**
     * 在完成请求处理后执行的方法。
     * 清除i18nLocaleHolder中的语言设置。
     */
    @Override
    public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
    
    
        try {
    
    
            i18nLocaleHolder.clear();
        } catch (Exception e) {
    
    
            log.error("清理语言设置时遇到错误:", e);
        }
    }

    public I18nLocaleHolder getI18nLocaleHolder() {
    
    
        return i18nLocaleHolder;
    }

    public void setI18nLocaleHolder(I18nLocaleHolder i18nLocaleHolder) {
    
    
        this.i18nLocaleHolder = i18nLocaleHolder;
    }

    /**
     * 根据语言设置获取Locale对象。
     *
     * @param lang 语言设置
     * @return Locale对象
     */
    private Locale getLocaleByLang(String lang) {
    
    
        return Optional.ofNullable(localeMap.get(lang))
                .orElseGet(() -> {
    
    
                    Locale locale = new Locale.Builder().setLanguageTag(lang).build();
                    localeMap.put(lang, locale);
                    return locale;
                });
    }

    /**
     * 从cookie中获取国际化语言设置。
     *
     * @param request HttpServletRequest对象
     * @return 国际化语言设置
     */
    private static String getLangFromCookies(HttpServletRequest request) {
    
    
        String lang = Optional.ofNullable(request.getCookies())
                .flatMap(cookies -> Arrays.stream(cookies)
                        .filter(cookie -> NAME_OF_LANGUAGE_SETTING.equals(cookie.getName()))
                        .findFirst())
                .map(Cookie::getValue)
                .orElse("");
        return lang;
    }

    /**
     * 从header中获取国际化语言设置。
     *
     * @param request HttpServletRequest对象
     * @return 国际化语言设置
     */
    private String getLangFromHeader(HttpServletRequest request) {
    
    
        String acceptLanguage = request.getHeader("Accept-Language");
        return Optional.ofNullable(acceptLanguage)
                .map(lang -> lang.split(","))
                .filter(array -> array.length > 0)
                .map(array -> array[0])
                .orElse("");
    }

}

I18nMessageResource loads internationalization configuration

Support local and database

package com.bdkjzx.project.i18n.repository;

import com.bdkjzx.project.i18n.config.I18nProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.BiFunction;

@Slf4j
public class I18nMessageResource extends AbstractMessageSource implements ResourceLoaderAware {
    
    
    private final Locale defaultLocale;

    @Autowired
    private List<I18nConfigDbLoader> i18NConfigDbLoaders;
    @Autowired
    private I18nProperties i18NProperties;
    @Lazy
    @Autowired(required = false)
    private I18nConfigDbLoader i18nConfigDbLoader;

    private final List<BiFunction<String, Locale, String>> getTextFunctionList = new ArrayList<>();

    public I18nMessageResource(Locale defaultLocale) {
    
    
        this.defaultLocale = defaultLocale;
    }

    @PostConstruct
    public void init() {
    
    
        if (this.i18NProperties.getEnable()) {
    
    
            getTextFunctionList.add(this::normalFinder);
            getTextFunctionList.add(this::languageFinder);
            getTextFunctionList.add(this::defaultLocaleFinder);

            if (i18NProperties.getUseLocale() && getParentMessageSource() != null) {
    
    
                getTextFunctionList.add(this::localFinder);
                getTextFunctionList.add(this::localDefaultFinder);
            }
        }
    }

    @Override
    public void setResourceLoader(@NonNull ResourceLoader resourceLoader) {
    
    
    }

    @Override
    protected MessageFormat resolveCode(@NonNull String code, @NonNull Locale locale) {
    
    
        String msg = getText(code, locale);
        return createMessageFormat(msg, locale);
    }

    @Override
    protected String resolveCodeWithoutArguments(@NonNull String code, @NonNull Locale locale) {
    
    
        return getText(code, locale);
    }

    /**
     * 这是加载国际化变量的核心方法,先从自己控制的内存中取,取不到了再到资源文件中取
     *
     * @param code   编码
     * @param locale 本地化语言
     * @return 查询对应语言的信息
     */
    private String getText(String code, Locale locale) {
    
    

        String result = getTextWithOutMark(code, locale);
        if (StringUtils.isEmpty(result)) {
    
    
            return result;
        }

        // 确实没有这项配置,确定是否要记录
        logger.warn("未找到国际化配置:" + code);
        if (i18NProperties.getMark()) {
    
    
            i18nConfigDbLoader.markInvalidCode(code);
        }
        //如果最终还是取不到,返回了NULL,则外面会用默认值,如果没有默认值,最终会返回给页面变量名称,所以变量名称尽量有含义,以作为遗漏配置的最后保障
        return code;
    }

    public String getTextWithOutMark(String code, Locale locale) {
    
    

        String result = "";
        // 从 function list中依次使用各种策略查询
        for (BiFunction<String, Locale, String> func : getTextFunctionList) {
    
    
            result = func.apply(code, locale);
            if (!StringUtils.isEmpty(result)) {
    
    
                return result;
            }
        }
        return result;
    }

    /**
     * 从指定locale获取值
     *
     * @param code   i18n code
     * @param locale 语言
     * @return 查询对应语言的信息
     */
    private String findValueFromLocale(String code, Locale locale) {
    
    
        String resultValue;
        for (I18nConfigDbLoader i18NConfigDbLoader : i18NConfigDbLoaders) {
    
    
            // 在loadE6I18nDictByLocaleEntity中做过缓存了
            resultValue = Optional.ofNullable(i18NConfigDbLoader.loadI18nDictByLocaleEntity())
                    .flatMap(localeMap -> Optional.ofNullable(localeMap.get(locale))
                            .map(codeMap -> codeMap.get(code)))
                    .orElse(null);
            if (!org.springframework.util.StringUtils.isEmpty(resultValue)) {
    
    
                return resultValue;
            }
        }
        return null;
    }

    // ======================================   查询字符的五种策略,加入function list   ======================================

    /**
     * 第一种情况:通过期望的语言类型查找
     *
     * @param code   国际化代码
     * @param locale 语言
     * @return 没找到时返回null
     */
    private String normalFinder(String code, Locale locale) {
    
    
        return findValueFromLocale(code, locale);
    }

    /**
     * 第二种情况,如果期望是 语言-国家 没有找到,那么尝试只找一下语言,比如zh-tw没找到,那就尝试找一下zh
     *
     * @param code   国际化代码
     * @param locale 语言
     * @return 没找到时返回null
     */
    private String languageFinder(String code, Locale locale) {
    
    
        if (locale.getLanguage() != null) {
    
    
            return findValueFromLocale(code, Locale.forLanguageTag(locale.getLanguage()));
        }
        return null;
    }

    /**
     * 第三种情况,如果没有找到 且不是默认语言包,则取默认语言包
     *
     * @param code   国际化代码
     * @param locale 语言
     * @return 没找到时返回null
     */
    private String defaultLocaleFinder(String code, Locale locale) {
    
    
        if (!Objects.equals(locale, defaultLocale)) {
    
    
            return findValueFromLocale(code, defaultLocale);
        }
        return null;
    }

    /**
     * 第四种情况,通过以上三种方式都没找到,那么尝试从本地配置文件加载期望的语言类型是否有
     *
     * @param code   国际化代码
     * @param locale 语言
     * @return 没找到时返回null
     */
    private String localFinder(String code, Locale locale) {
    
    
        String value = Objects.requireNonNull(getParentMessageSource()).getMessage(code, null, null, locale);
        if (logger.isDebugEnabled() && !StringUtils.isEmpty(value)) {
    
    
            logger.debug("从配置文件" + locale.toString() + "找到变量" + code + "=" + value);
        }
        return value;
    }

    /**
     * 第五种情况,如果没有找到,则从本地配置文件加载默认的语言类型是否有
     *
     * @param code   国际化代码
     * @param locale 语言
     * @return 没找到时返回null
     */
    private String localDefaultFinder(String code, Locale locale) {
    
    
        if (!Objects.equals(locale, defaultLocale)) {
    
    
            return this.localFinder(code, defaultLocale);
        }
        return null;
    }

}

pom file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.bdkjzx.project</groupId>
	<artifactId>i18n-spring-boot-starter</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>i18n-spring-boot-starter</name>
	<description>Spring boot 国际化配置</description>
	<properties>
		<java.version>8</java.version>
		   <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<!--版本支持到2.7.x-->
        <spring-boot.version>2.0.3.RELEASE</spring-boot.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
	</dependencies>
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
			      </dependencies>
    </dependencyManagement>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>8</source>
					<target>8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

3. Source code address

https://github.com/wangshuai67/i18n-spring-boot-starter/
Insert image description here Hello everyone, I am Bingdian. Today’s native SpringBoot international configuration component supports local configuration and database configuration content. That’s all. The writing is a bit rough. . If you have questions or insights, you can leave a message in the comment area.

Guess you like

Origin blog.csdn.net/wangshuai6707/article/details/132818517