分布式微服务系统架构第111集:spring三大特性,跨境电商、SaaS 平台、B2B 管理后台实现多语言国际化

221 阅读8分钟

加群联系作者vx:xiaoda0423

仓库地址:webvueblog.github.io/JavaPlusDoc…

1024bat.cn/

🌍 一、Java 后端国际化方案核心概念

模块说明
资源文件 messages_xx.properties存放不同语言的文本内容(英文、中文、西班牙语等)
Locale 区域识别根据请求中的语言标识(如 Accept-Languagelang 参数)来选择语言资源
国际化接口支持所有返回文案可动态切换语言
与前端协作接收前端语言标识,如 lang=en-US,返回对应语言的提示/文案

🗂️ 二、资源文件结构(以 Spring Boot 为例)

src/main/resources/
├── messages.properties          # 默认中文
├── messages_en.properties       # 英文
├── messages_es.properties       # 西班牙语

示例内容:

messages.properties

user.login.success=登录成功
user.login.fail=用户名或密码错误

messages_en.properties

user.login.success=Login successful
user.login.fail=Incorrect username or password

⚙️ 三、Spring Boot 国际化配置

1. 配置 MessageSource Bean

@Configuration
public class I18nConfig {

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:messages"); // messages*.properties
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocaleResolver localeResolver() {
        // 支持从请求中获取语言参数(?lang=en_US)
        SessionLocaleResolver slr = new SessionLocaleResolver();
        slr.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return slr;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang"); // 从参数 lang 解析语言
        return interceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

🧠 四、使用国际化消息

注入 MessageSource 获取翻译

@Autowired
private MessageSource messageSource;

public String getMessage(String code, Locale locale) {
    return messageSource.getMessage(code, null, locale);
}

或在控制器中:

@GetMapping("/login")
public ResponseEntity<?> login(@RequestParam String lang) {
    Locale locale = LocaleContextHolder.getLocale(); // 自动从请求参数或 header 获取
    String msg = messageSource.getMessage("user.login.success", null, locale);
    return ResponseEntity.ok(Map.of("msg", msg));
}

📦 五、前端如何传语言?

前端方式示例
URL 参数https://api.xxx.com/login?lang=en
Header 头Accept-Language: en-US
JWT / Cookie后台从 Token 中读取语言偏好

🔌 六、进阶功能(可选)

功能描述
动态多语言维护文案从数据库或后台配置平台维护
国际化组件化国际化返回结构:code + message 统一封装
校验提示国际化使用 @Validated 时错误提示也可国际化
异常提示国际化ExceptionMessageResolver 支持多语言错误处理

📋 七、结合跨境业务建议

场景建议
多语言后台系统配置 语言切换按钮,通过 lang 参数调用接口
多国家独立站每个站点配置语言偏好,支持用户语言回显
多语言邮件模板后端发送通知邮件时,按用户语言选择模板

✅ 总结

技术点是否可配合
Spring Boot✅ 完美支持国际化(i18n)配置
前后端分离✅ 提供语言标识字段,服务端按需响应多语言
跨境电商/SaaS 场景✅ 支持多语言商品名、订单状态、支付文案、后台提示等

📦 一、Redis 缓存系统常见应用场景

场景示例
热门商品缓存电商首页、详情页秒开
订单信息缓存避免频繁访问数据库
登录态存储Token、Session 缓存
接口幂等校验基于 Redis 保证请求唯一
防刷频控限制单位时间内的请求次数

🔥 二、热点缓存(高频数据缓存)

✅ 目标:

避免对数据库频繁查询相同数据,使用 Redis 缓存住热点。

示例代码:

public Product getProduct(Long productId) {
    String cacheKey = "product:" + productId;
    String json = redisTemplate.opsForValue().get(cacheKey);
    
    if (StringUtils.hasText(json)) {
        return JSON.parseObject(json, Product.class);
    }

    // 查询数据库
    Product product = productMapper.selectById(productId);
    if (product != null) {
        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), 10, TimeUnit.MINUTES);
    }
    return product;
}

🚫 三、缓存穿透(查询不存在的数据)

✅ 场景:

恶意请求大量不存在的 key,缓存未命中,数据库压力骤增。

✅ 解决方案:

✅ 1. 布隆过滤器(Bloom Filter)

  • 将所有合法 ID 加入布隆过滤器,非法请求直接拦截。
if (!bloomFilter.contains(id)) {
    return null; // 无效请求
}

✅ 2. 缓存空值

  • 将查询结果为 null 也缓存一段时间,避免频繁打数据库。
if (product == null) {
    redisTemplate.opsForValue().set(cacheKey, "", 2, TimeUnit.MINUTES);
}

🧊 四、缓存雪崩(大面积缓存同时过期)

✅ 场景:

缓存集中在某一时刻过期,导致短时间大量请求打到数据库。

✅ 解决方案:

方法描述
缓存过期加随机给过期时间增加随机值,避免同时失效
数据预热启动时或定时预加载关键数据到缓存
多级缓存本地缓存 + 分布式缓存(如 Guava + Redis)
// 设置过期时间随机值 10-15 分钟
long timeout = 10 + new Random().nextInt(5);
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MINUTES);

🔐 五、缓存击穿(单个热点数据失效)

✅ 场景:

某个高并发访问的 key 刚好失效,大量请求同时打到数据库。

✅ 解决方案:

✅ 1. 分布式锁保护(推荐使用 Redisson)

RLock lock = redissonClient.getLock("lock:product:" + id);
if (lock.tryLock()) {
    try {
        // 查询数据库并写入缓存
    } finally {
        lock.unlock();
    }
} else {
    // 其他线程睡眠等待缓存
    Thread.sleep(50);
    return redisTemplate.opsForValue().get(cacheKey);
}

✅ 2. 本地互斥锁(Guava / Caffeine 本地缓存配合)

🧠 六、封装模板(伪代码)

可以将缓存逻辑封装成统一的工具类或注解方式(配合 AOP):

public <T> T queryWithCache(String keyPrefix, Long id, Function<Long, T> dbFallback) {
    String key = keyPrefix + id;
    String json = redis.get(key);
    if (StringUtils.hasText(json)) {
        return parse(json);
    }

    // 加锁防击穿
    RLock lock = redisson.getLock("lock:" + key);
    lock.lock();

    try {
        // 二次检查缓存
        json = redis.get(key);
        if (StringUtils.hasText(json)) {
            return parse(json);
        }

        // 查数据库
        T data = dbFallback.apply(id);
        if (data == null) {
            redis.set(key, "", 2, TimeUnit.MINUTES); // 缓存空值
        } else {
            redis.set(key, toJson(data), 10, TimeUnit.MINUTES);
        }
        return data;
    } finally {
        lock.unlock();
    }
}

✅ 七、建议的技术选型

技术组件推荐工具
缓存客户端Spring Data Redis + Lettuce
分布式锁Redisson
本地缓存Caffeine(支持淘汰 + 异步刷新)
布隆过滤器Guava BloomFilter / RedisBitmap 实现
可视化监控RedisInsightPrometheus + Grafana

📌 八、附加功能推荐

  • 定时刷新热点缓存(配合异步线程池)
  • 缓存预加载、预热机制
  • 缓存版本号控制(缓存双写一致性)
  • 接口缓存(AOP + 注解)

Spring 框架之所以成为 Java 后端开发的主流框架,很大程度上是因为它具备三大核心特性,它们分别是:


🧠 一、IOC(控制反转,Inversion of Control)

✅ 作用:

把对象的创建和依赖关系交给 Spring 容器统一管理,而不是由我们手动 new

✅ 理解方式:

原来我们是自己控制对象的创建,现在是反转给容器来帮我们创建和注入。

✅ 举例:

@Component
public class UserService {
    @Autowired
    private OrderService orderService;
}

✅ 本质:

  • IoC 容器会扫描 @Component 注解的类,将其放入容器
  • 然后通过 @Autowired 自动注入所需依赖

⚙️ 二、AOP(面向切面编程,Aspect Oriented Programming)

✅ 作用:

与业务无关的通用逻辑(如日志、事务、权限)从业务代码中抽离出来,形成切面统一管理。

✅ 应用场景:

  • 日志打印
  • 方法调用监控
  • 权限校验
  • 接口防刷
  • 异常统一处理

✅ 示例代码:

@Aspect
@Component
public class LogAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("调用方法:" + joinPoint.getSignature().getName());
    }
}

🧩 三、事务管理(Transaction Management)

✅ 作用:

保证数据库操作的一致性、隔离性等事务特性。

✅ 使用方式:

只需加上一个注解即可实现事务的开启和回滚。

@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        // 操作订单表
        // 操作库存表
        // 如果出错自动回滚
    }
}

✅ 事务传播机制(核心内容):

  • REQUIRED:默认,当前有事务就加入,否则新建一个
  • REQUIRES_NEW:强制新建事务
  • NESTED:嵌套事务(可以独立回滚)

📦 总结:Spring 三大特性作用表

特性简称功能
控制反转IoC统一管理 Bean 的生命周期和依赖注入
面向切面编程AOP抽离日志、事务、权限等公共逻辑
声明式事务Tx让数据库操作更安全、可控

Spring 中的事务功能(@Transactional

✅ 一、事务生效的必要条件

条件说明
1. 方法必须被 Spring 容器管理的类调用即类上必须被 @Service@Component@Repository 标注
2. 方法必须通过 代理对象 调用Spring 事务是基于 AOP 实现的,需要经过代理
3. 方法必须是 public 访问级别因为 Spring 默认使用 JDK 或 CGLIB 动态代理,私有方法无法被代理拦截
4. 有真正的 数据库操作,并被连接管理Spring 的事务本质是对数据库连接的事务控制
5. 抛出的异常类型需匹配默认策略默认只回滚 运行时异常(RuntimeException)及其子类

❌ 二、事务失效的常见情况(重点!)

🧨 场景 1:方法内部自调用,事务不生效

@Transactional
public void methodA() {
    methodB(); // ⚠️ 同类内部调用,不会经过代理,事务失效
}

@Transactional
public void methodB() {
    // 数据库操作
}

✅ 解决方式:使用代理对象调用,如通过 AopContext.currentProxy() 或将方法拆到新类中。

🧨 场景 2:方法不是 public,事务不生效

@Transactional
void createOrder() {
    // ⚠️ 非 public 方法,不会被代理,事务失效
}

✅ 正确写法:

@Transactional
public void createOrder() {
    // 生效 ✅
}

🧨 场景 3:异常被 catch 了,事务不会回滚

@Transactional
public void process() {
    try {
        // 这里会报错
        int a = 1 / 0;
    } catch (Exception e) {
        log.error("异常", e);
        // ⚠️ 捕获了异常,事务不会自动回滚
    }
}

✅ 正确处理方式:

  • 抛出异常让事务感知
  • 或手动触发回滚:
catch (Exception e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

🧨 场景 4:抛出的是检查异常,事务不会回滚

默认 Spring 只对 RuntimeException 及其子类 自动回滚。

@Transactional
public void save() throws Exception { // 抛出的是 checked exception
    throw new Exception("checked 异常"); // ⚠️ 默认不会回滚
}

✅ 正确做法:

@Transactional(rollbackFor = Exception.class) // 手动指定回滚策略
public void save() throws Exception {
    throw new Exception("checked 异常");
}

🧨 场景 5:数据库未开启事务支持 或 非数据库操作

例如:

  • 操作 Redis、MQ,不受事务控制
  • 使用了数据库但某些 ORM 配置没有启用事务(如 MyBatis 没有开启事务管理器)

✅ 正确做法:检查是否使用了 DataSourceTransactionManager,以及操作是否真正落到了数据库连接上。

🧠 三、事务失效的图解总结

失效原因说明
自调用未经过代理,不会触发事务切面
public 方法Spring AOP 无法代理
异常被 catch事务未感知到异常
抛出 checked exception默认不会回滚
不操作数据库没有真正事务
Redis、MQ 操作不受数据库事务控制

🎯 四、如何确保事务生效?

  • @Transactional 一般加在 public 的业务方法上

  • 保证方法是通过 代理对象 调用的(不是 this.xx())

  • 明确异常抛出并让 Spring 能感知(或者加 rollbackFor

  • 日志调试建议打开:

    logging:
      level:
        org.springframework.transaction: DEBUG