基于spring boot的JsonSerializer 业务内容国际化

这篇具有很好参考价值的文章主要介绍了基于spring boot的JsonSerializer 业务内容国际化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

说起国际化,真的是老生常谈了。后端有各种i18n的依赖组件,springboot本身也支持i18n的设置,前端vue也有i18n的设置,这些常规操作就不提了,大家可以去搜索其他博客,写的都很详细。

本篇博客主要写的是业务内容国际化。举一个最常用最简单的例子,学生选课,课程有"语文","数学","英语"。这个课程也是一张业务表,随着课程的增多数据是逐渐增多的。一个学生要查看自己选择的课程时,如何根据语言进行国际化的反显"数学"还是"mathematics"。

最开始我拿到这个需求的时候,很挠头,怎么办,难得不是把这个需求做出来,这个需求实现得方式很多:

  1. 多建冗余字段,把”数学“和”mathematics“都存到表里,这样有明显得缺点,语言增多时需要一直在表里加字段。
  2. 建一张code、language、value的对应关系,查询数据的时候根据code和language进行value的匹配,这种缺陷也很明显,业务侵入性很强。

我要做的事情是让业务开发人员在无感知的情况下或侵入性很小的情况下把需求实现。提到侵入性小,大家很容易联想到切面编程AOP。我个人认为AOP最好用的地方就是能拿到自定义注解,通过在java类或者java方法上增加注解,在切面获取引入的东西并将我们相要的东西织入。

灵感一来,我们就开干。

一、建表,并存储基础数据

表的作用是能将各种code对应的各种语言的各种value进行匹配,建表比写在配置文件的好处是显而易见的,因为我们做的是业务内容的国际化,而不是定死的几个值得国际化,我们需要根据业务动态得调整内容。这个表的数据可以开一个接口,业务数据发生变化时,可以直接调用这个接口,对表中数据进行更新。

表结构如下:

基于spring boot的JsonSerializer 业务内容国际化

 LANGUAGE_ID 主键

LANGUAGE_KEY 存在业务表中得业务标识

LANGUAGE 语言标识

LANGUAGE_VALUE 国际化后的值

MODEL 模块名称,主要防止KEY重复,同一个key在不同的业务中代表的含义不同。

以上面选课为例,该表存放的值为

1 course math en mathematics

2 course math zh-CN 数学

二、获取表中数据放入缓存

数据咱们都有了,怎么把数据拿出来用呢,每次查库?肯定不现实,我们应该提前把准备好,放在缓存中,谁想用直接取。缓存有多种方式。我们做jvm和redis两种,让大家做选择,追求效率就用jvm缓存,不求效率就用redis,对本身服务影响小一些。

1、首先定义实体类

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 系统语言ResultDTO
 */
@Getter
@Setter
@ToString
public class SysLanguageConfig {


    private Long languageId;

    private String model;

    private String languageKey;

    private String language;

    private String languageValue;
    private long currentPage;
    private long pageSize;
}

2、获取数据并缓存的配置类

package com.cnhtc.hdf.wf.common.i18n;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.util.*;
import java.util.concurrent.*;

@Configuration  
@EnableFeignClients(clients = {SysLanguageConfigService.class})
@Slf4j
public class LanguageCahceConfigration {
    public static ConcurrentHashMap<String, String> localCacheMap = new ConcurrentHashMap<>(); //本地存储缓存的map

    /**
     * 存储redis 热点数据
     */
    public static ConcurrentHashMap<String, String> redisHotspotCacheMap = new ConcurrentHashMap<>();

    public final static String CACHE_KEY_JOIN_SYMBOL = "_";

    private static Boolean i18n;

    @Value("${i18nPageSize: 5000}")
    private Integer i18nPageSize;

    @Value("${i18nEnableInitDataParallel: false}")
    private Boolean i18nEnableInitDataParallel;

    /**
     * 是否开启redis热点数据缓存,默认不开启
     */
    private static Boolean i18nEnableRedisHotspotCache;

    /**
     * 开启缓存模式
     * local MAP
     * redis
     */
    private static String i18nEnableCacheMode;

    private final static String CACHE_MODE = "local";

    /**
     * 多少个元素拆分一个List
     */
    private final Integer splitListSize = 10;


    /**
     * 批量插入 条数
     */
    private final Integer REDIS_BATCH_SAVE_SIZE = 5000;

    /**
     * 失效时间
     */
    private final long EXPIRE_SECONDS = 3600 * 1000;

    @Autowired
    private SysLanguageConfigService sysLanguageConfigService;


    @Bean
    public SysLanguageConfigServiceFallback sysLanguageConfigServiceFallback() {
        return new SysLanguageConfigServiceFallback();
    }

    public LanguageCahceConfigration() {
        System.out.println("------------------- 加载 LanguageCahceConfigration ----------------------------------");
    }

    @Scheduled(initialDelay = 1000, fixedRateString = "${i18nScheduledFixedRate:3600000}")
    public void setLanguageCacheMap() {
        if (i18n) {
            if (!CACHE_MODE.equals(i18nEnableCacheMode)) {
                return;
            }
            CopyOnWriteArrayList<SysLanguageConfig> allList = new CopyOnWriteArrayList<>();
            StopWatch sw = new StopWatch();
            try {
                sw.start("数据查询");
                if (i18nEnableInitDataParallel) {
                    this.selectDataCompletableFuture(allList);
                } else {
                    this.selectData(allList);
                }
                sw.stop();
            } catch (Exception e) {
                e.printStackTrace();
                allList.clear();
            }
            log.debug("allList size = {}", allList.size());
            sw.start("本地缓存");
            localCacheMap.clear();
            localCacheMap.putAll(this.getCacheDataMap(allList));
            sw.stop();
            log.warn("初始化i18n 缓存耗时 , {}", sw.prettyPrint());
            log.warn("初始化i18n 缓存总耗时 , {}", sw.getTotalTimeSeconds());
        }
    }


    /**
     * 循环查询数据
     *
     * @param allList 数据集合
     */
    private void selectData(CopyOnWriteArrayList<SysLanguageConfig> allList) {
        int page = 1;
        boolean isContinue = false;
        do {
            SysLanguageConfig sysLanguageConfig = new SysLanguageConfig();
            sysLanguageConfig.setCurrentPage(page);
            sysLanguageConfig.setPageSize(i18nPageSize);
            Page<SysLanguageConfig> result = sysLanguageConfigService.getAll(sysLanguageConfig);
            if (result != null && !CollectionUtils.isEmpty(result.getRecords())) {
                allList.addAll(result.getRecords());
                if (result.getPages() > page) {
                    isContinue = true;
                    page = page + 1;
                } else {
                    isContinue = false;
                }
            }
        } while (isContinue);
    }


    /**
     * 异步分页查询数据
     *
     * @param allList 数据集合
     * @throws Exception 异常
     */
    private void selectDataCompletableFuture(CopyOnWriteArrayList<SysLanguageConfig> allList) throws Exception {
        Page<SysLanguageConfig> result = this.getData();
        if (result != null && result.getPages() > 0) {
            allList.addAll(result.getRecords());
            if (result.getPages() > 1) {

                ForkJoinPool pool = new ForkJoinPool();
                List<Integer> pageList = new ArrayList<>();
                for (int i = 2; i <= result.getPages(); i++) {
                    pageList.add(i);
                }
                List<List<Integer>> partition = Lists.partition(pageList, splitListSize);
                for (List<Integer> pages : partition) {
                    List<CompletableFuture<Void>> futureList = new ArrayList<>();
                    for (Integer page : pages) {
                        SysLanguageConfig param = new SysLanguageConfig();
                        param.setCurrentPage(page);
                        param.setPageSize(i18nPageSize);

                        CompletableFuture<Void> future = CompletableFuture.runAsync(() ->
                                allList.addAll(sysLanguageConfigService.getAll(param).getRecords()), pool);
                        futureList.add(future);
                    }
                    CompletableFuture<Void> allSources = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()]));
                    allSources.get();
                }
            }
        }
    }

    /**
     * 获取数据
     * @return Page<SysLanguageConfig>
     */
    private Page<SysLanguageConfig> getData(){
        SysLanguageConfig sysLanguageConfig = new SysLanguageConfig();
        sysLanguageConfig.setCurrentPage(1);
        sysLanguageConfig.setPageSize(i18nPageSize);
        return sysLanguageConfigService.getAll(sysLanguageConfig);
    }

    /**
     * 批量插入并设置 失效时间,但是性能慢
     *
     * @param map 数据
     */
    private void redisPipelineInsert(ConcurrentHashMap<String, String> map) {
        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
        RedisSerializer<String> serializer = stringRedisTemplate.getStringSerializer();
        stringRedisTemplate.executePipelined(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                map.forEach((key, value) -> {
                    connection.set(serializer.serialize(key), serializer.serialize(value), Expiration.seconds(EXPIRE_SECONDS), RedisStringCommands.SetOption.UPSERT);
                });
                return null;
            }
        }, serializer);
    }

    /**
     * 批量插入后 异步设置失效时间
     *
     * @param map 数据
     */
    //@Async
    public void setExpire(ConcurrentHashMap<String, String> map) {
        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
        map.forEach((k, v) -> stringRedisTemplate.expire(k, EXPIRE_SECONDS, TimeUnit.SECONDS));
    }

    /**
     * 刷新redis缓存
     */
    @XxlJob("i18nRefreshRedisCache")
    public void refreshRedisCache() {
        XxlJobHelper.log("回调任务开始");
        if (i18n) {
            if (CACHE_MODE.equals(i18nEnableCacheMode)) {
                log.error("i18n国际化配置本地缓存,请勿用redis刷新缓存");
            }

            CopyOnWriteArrayList<SysLanguageConfig> allList = new CopyOnWriteArrayList<>();
            StopWatch sw = new StopWatch();
            try {
                sw.start("数据查询");
                if (i18nEnableInitDataParallel) {
                    this.selectDataCompletableFuture(allList);
                } else {
                    this.selectData(allList);
                }
                sw.stop();
            } catch (Exception e) {
                e.printStackTrace();
                allList.clear();
            }
            sw.start("redis缓存");
            StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
            if (ObjectUtils.isEmpty(stringRedisTemplate)) {
                throw new BaseException(ErrorEnum.NOTHROWABLE_ERROR, "StringRedisTemplate is null");
            }
            redisHotspotCacheMap.clear();
            ConcurrentHashMap<String, String> cacheDataMap = this.getCacheDataMap(allList);
            List<Map<String, String>> maps = splitMap(cacheDataMap, REDIS_BATCH_SAVE_SIZE);
            // multiSet 批量插入,key值存在会覆盖原值
            maps.forEach(data -> stringRedisTemplate.opsForValue().multiSet(data));
            sw.stop();
            log.warn("初始化i18n redis缓存耗时 , {}", sw.prettyPrint());
            log.warn("初始化i18n redis缓存总耗时 , {}", sw.getTotalTimeSeconds());
        }
        XxlJobHelper.log("回调任务结束");
    }

    /**
     * 刷新local缓存
     */
    //@XxlJob("i18nRefreshLocalCache")
    public ResponseDTO refreshLocalCache() {
        if (CACHE_MODE.equals(i18nEnableCacheMode)) {
            this.setLanguageCacheMap();
            return new ResponseDTO(SysErrEnum.SUCCESS);
        }
        return new ResponseDTO(SysErrEnum.ERROR.code(), "i18n国际化配置Redis缓存,请勿用本地刷新缓存");
    }

    /**
     * 获取缓存数据
     *
     * @param allList
     * @return
     */
    private ConcurrentHashMap<String, String> getCacheDataMap(List<SysLanguageConfig> allList) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        allList.parallelStream().forEach(config -> map.put(config.getModel() + CACHE_KEY_JOIN_SYMBOL + config.getLanguageKey() + CACHE_KEY_JOIN_SYMBOL + config.getLanguage(), config.getLanguageValue()));
        log.warn("map size:{}", map.size());
        return map;
    }

    /**
     * 获取缓存数据值
     *
     * @param key key
     * @return value
     */
    public static String getCacheValueByKey(String key) {
        if (i18n) {
            String value;
            if (CACHE_MODE.equals(i18nEnableCacheMode)) {
                value = localCacheMap.get(key);
                log.debug("多语言转换:本地缓存数量 = {}, key = {}", +localCacheMap.values().size(), key);
            } else {
                if (i18nEnableRedisHotspotCache) {
                    if (redisHotspotCacheMap.containsKey(key)) {
                        value = redisHotspotCacheMap.get(key);
                    } else {
                        StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
                        value = stringRedisTemplate.opsForValue().get(key);
                        if (StringUtils.isNotBlank(value)) {
                            // 缓存热点数据
                            redisHotspotCacheMap.put(key, value);
                        }
                    }
                } else {
                    StringRedisTemplate stringRedisTemplate = ApplicationContextUtil.getBean(StringRedisTemplate.class);
                    value = stringRedisTemplate.opsForValue().get(key);
                }
            }
            return value;
        }
        return null;
    }

    @Value("${i18nEnableCacheMode: local}")
    private void setI18nEnableCacheMode(String i18nEnableCacheMode) {
        LanguageCahceConfigration.i18nEnableCacheMode = i18nEnableCacheMode;
    }

    @Value("${i18nEnableRedisHotspotCache: false}")
    private void setI18nEnableRedisHotspotCache(Boolean i18nEnableRedisHotspotCache) {
        LanguageCahceConfigration.i18nEnableRedisHotspotCache = i18nEnableRedisHotspotCache;
    }

    @Value("${i18n: false}")
    private void setI18n(Boolean i18n) {
        LanguageCahceConfigration.i18n = i18n;
    }

    /**
     * Map拆分 (指定分组大小)
     *
     * @param map       Map
     * @param chunkSize 每个分组的大小 (>=1)
     * @param <K>       Key
     * @param <V>       Value
     * @return 子Map列表
     */
    private <K, V> List<Map<K, V>> splitMap(Map<K, V> map, int chunkSize) {
        if (Objects.isNull(map) || map.isEmpty() || chunkSize < 1) {
            //空map或者分组大小<1,无法拆分
            return Collections.emptyList();
        }

        int mapSize = map.size(); //键值对总数
        int groupSize = mapSize / chunkSize + (mapSize % chunkSize == 0 ? 0 : 1); //计算分组个数
        List<Map<K, V>> list = Lists.newArrayListWithCapacity(groupSize); //子Map列表

        if (chunkSize >= mapSize) { //只能分1组的情况
            list.add(map);
            return list;
        }

        int count = 0; //每个分组的组内计数
        Map<K, V> subMap = Maps.newHashMapWithExpectedSize(chunkSize); //子Map

        for (Map.Entry<K, V> entry : map.entrySet()) {
            if (count < chunkSize) {
                //给每个分组放chunkSize个键值对,最后一个分组可能会装不满
                subMap.put(entry.getKey(), entry.getValue());
                count++; //组内计数+1
            } else {
                //结束上一个分组
                list.add(subMap); //当前分组装满了->加入列表

                //开始下一个分组
                subMap = Maps.newHashMapWithExpectedSize(chunkSize); //新的分组
                subMap.put(entry.getKey(), entry.getValue()); //添加当前键值对
                count = 1; //组内计数重置为1
            }
        }

        list.add(subMap);  //添加最后一个分组
        return list;
    }
}

整段代码其中区分了本地缓存、redis缓存等等,还有就是查刚才数据库表里得数据,因为我们才用了微服务得架构,所以获取数据得部分是通过feign的方式获取的,大家可以替换成自己的方法。另外,开启redis缓存的部分可以取舍,没必要这么完善,保留一种即可。本地缓存的定时任务是springboot的,redis的定时任务是xxl-job的,这些技术栈都可以替换。

其中最重要的一点,redis比本地缓存慢很多,100条数据的国际化反显,速度会差20倍。为什么差怎么多,接下来就到关键内容了。

三、注解定义

注解定义的意义就是在序列化的时候,能通过注解拿到切入点并获取注解的内容


import java.lang.annotation.*;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(
        using = I18nSerializer.class
)
public @interface I18n {

    String model() default "common";
    String language() default "";
    String key() default "";
}

四、基于JsonSerializer的序列化处理,进行国际化转换

大家现在都在用springboot的restController,也就是说,前后端分离之后,前后端的交互就是json,在controller返回的内容其实就是一个实体对象或者集合,那这个实体对象或者集合是怎么转换成json的,就是通过springboot中引入的jackson来实现的,具体实现原理不多说。

我们只需要知道,写一个子类,来继承JsonSerializer和实现ContextualSerializer就能实现序列化的时候进行织入操作。

其中language是通过header从前端传递过来的。

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.stdp.hdf.wf.common.core.constants.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

@Slf4j
public class I18nSerializer extends JsonSerializer<String> implements ContextualSerializer {

    private String model;

    private String language;

    private String key;

    public I18nSerializer(String model, String language, String key) {
        this.model = model;
        this.language = language;
        this.key = key;
    }

    public I18nSerializer() {
    }

    @Override
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        String requestLanguage = null;
        String mapkey = s;
        if (StringUtils.isBlank(language)) {
            requestLanguage = getLanguage();
        } else {
            requestLanguage = language;
        }
        if (StringUtils.isNotBlank(requestLanguage)) {
            if (StringUtils.isNotBlank(key)) {
                Object o = jsonGenerator.getCurrentValue();
                mapkey = getPropertyValue(o, key).toString();
            }
            String keyString = model + LanguageCahceConfigration.CACHE_KEY_JOIN_SYMBOL + mapkey + LanguageCahceConfigration.CACHE_KEY_JOIN_SYMBOL + requestLanguage;
            String keyName = LanguageCahceConfigration.getCacheValueByKey(keyString);
            if (StringUtils.isBlank(keyName)) {
                keyName = s;
            }
            jsonGenerator.writeString(keyName);
        } else {
            jsonGenerator.writeString(s);
        }

    }


    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        if (beanProperty != null) { // 为空直接跳过
            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { // 非 String 类直接跳过
                I18n i18n = beanProperty.getAnnotation(I18n.class);
                if (i18n == null) {
                    i18n = beanProperty.getContextAnnotation(I18n.class);
                }
                if (i18n != null) { // 如果能得到注解,就将注解的 value 传入 I18nSerializer
                    return new I18nSerializer(i18n.model(), i18n.language(), i18n.key());
                }
            }
            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
        }
        return serializerProvider.findNullValueSerializer(beanProperty);
    }

    public String getLanguage() {
        //直接从request中获取language信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }

        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

        return request.getHeader(Constants.LANGUAGE);
    }

    public Object getPropertyValue(Object t, String objProperty) {
        Map<String, String> objMap = null;
        try {
            objMap = BeanUtils.describe(t);
            if (objMap.get(objProperty) != null) {
                return objMap.get(objProperty);
            }
            return "";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

五、使用

还是以选课为例,返回的json信息,CourseName自动就转成了对应的语言。文章来源地址https://www.toymoban.com/news/detail-492518.html

@Getter
@Setter
@ToString
public Course implements Serializable {
    private String courseCode; //课程编号 

    @I18n(model = "course",key = "courseCode")
    private String courseName; //课程名称

}

到了这里,关于基于spring boot的JsonSerializer 业务内容国际化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Boot 项目设计业务操作日志功能,写得太好了!

    很久以前都想写这篇文章,一直没有空,但直到现在我对当时的情景还有印象,之所以有印象是因为需求很简单,业务操作日志的记录与查询的功能,但是具体实现真的很烂,具体的烂法会在反面示例里细说,领导以及客户层面很认可,一系列迷之操作,让我印象深刻。 客户

    2024年02月11日
    浏览(40)
  • 大型集团借力泛微搭建语言汇率时区统一、业务协同的国际化OA系统

    国际化、全球化集团,业务遍布全世界,下属公司众多,集团对管理方式和企业文化塑造有着很高的要求。不少大型集团以数字化方式助力全球统一办公,深化企业统一管理。 面对大型集团全球化的管理诉求,数字化办公系统作为集团日常使用的平台,自然需要适应企业管理

    2024年02月07日
    浏览(46)
  • spring boot + Apache tika 实现文档内容解析

    Apache tika 是 Apache 开源的一个文档解析工具。 Apache Tika 可以解析和提取一千多种不同的文件类型(如PPT、XLS和PDF)的内容和格式,并且 Apache Tika 提供了多种使用方式,既可以使用图形化操作页面(tika-app),又可以独立部署(tika-server)通过接口调用,还可以引入到项目中使用。

    2024年02月14日
    浏览(34)
  • RabbitMQ和spring boot整合及其他内容

    在现代分布式应用程序的设计中,消息队列系统是不可或缺的一部分,它为我们提供了解耦组件、实现异步通信和确保高性能的手段。RabbitMQ,作为一款强大的消息代理,能够协助我们实现这些目标。在本篇CSDN博客中,我们将探讨一些高级主题,包括RabbitMQ与Spring Boot的整合、

    2024年02月07日
    浏览(43)
  • 【SpringBoot应用篇】Spring Boot 配置HTTP 响应内容压缩

    5、默认情况下,要执行压缩,响应的长度至少为 2048 字节,可以通过 server.compression.min-response-size 属性配置。 6、默认情况下,仅当响应的内容类型为以下内容之一时,才会对其进行压缩,可以通过 mime-types 属性配置:text/html,text/xml,text/plain,text/css,text/javascript,application/javasc

    2024年02月16日
    浏览(45)
  • 【Spring Boot】四种核心类的依赖关系:实体类、数据处理类、业务处理类、控制器类

    //1.配置项目环境,创建Spring Boot项目。 //2.数据库设置,配置数据库。 //3.创建实体类,映射到数据库。 //4.创建数据处理层类,Repository //5.创建业务处理类,Service类 //6.创建控制器类,Controller类 Article.java java import javax.persistence.Entity; import javax.persistence.GeneratedValue; import java

    2024年02月11日
    浏览(43)
  • Spring Boot Maven package时显式的跳过test内容

    在pom.xml的编译插件部分显式的增加一段内容: maven package时可以跳过Spring Boot项目中的各种TEST代码,避免一些打包中的错误。

    2024年02月13日
    浏览(40)
  • 自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势

    自定义redission装配和集成分布式开源限流业务组件ratelimiter-spring-boot-starter的正确姿势   由于使用了redisson-spring-boot-starter,在自定义redisson装配的时候会被redisson-spring-boot-starter里面的start默认装配了,同时在使用开源分布式限流组件ratelimiter-spring-boot-starter的时候,这个里面

    2024年02月07日
    浏览(55)
  • Spring Boot 集成 WebSocket 实例 | 前端持续打印远程日志文件更新内容(模拟 tail 命令)

    这个是我在 CSDN 的第一百篇原则博文,留念😎 先说下项目结构,后端基于 Spring Boot 3,前端为 node.js 开发的控制台程序。现在希望能够在前端模拟 tail 命令,持续输出后端的日志文件。 这个方案实施较为简单,通过前端不断(定时)发起请求,并携带已读的内容坐标(posi

    2024年03月18日
    浏览(46)
  • Spring boot之WEB 开发-静态资源访问--自定义转换器--处理JSON--内容协商

    在线文档: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-web-applications 1. 只要静态资源放在类路径下: /static 、/public 、/resources 、/META-INF/resources可以被直接访问- 对应文件WebProperties.java 2. 常见静态资源:JS、CSS 、图片(.jpg .png .gif .bmp .svg)、字体

    2024年02月09日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包