适配器模式实战指南:让不兼容的接口无缝协作

一、生活场景引入(5秒理解概念)

1️⃣ 手机充电器:万能转换头的秘密

想象你带着中国插头的手机到英国旅行------两国的插座形状完全不同!这时一个转换头(Adapter)就能让手机充电器适配英国插座,整个过程:

  • 旧设备:中国标准插头(需要三方接口)
  • 新插座:英国标准接口(现有系统规范)
  • 转换头:把中国插头转换成英国插座兼容的形态

这就是适配器的本质:在不修改原有插头和插座的情况下,通过中间层解决兼容问题。


2️⃣ 翻译官:跨语言沟通的桥梁

当中国工程师与美国客户开会时,双方语言不通。翻译官(Adapter)的介入:

  • 中方说中文 → 翻译官接收 → 转译为英文 → 美方理解
  • 美方说英文 → 翻译官接收 → 转译为中文 → 中方理解

技术映射:两个系统使用不同数据格式时,适配器就像翻译官,在中间做协议转换。


3️⃣ 技术世界的接口冲突(Mermaid图表解读)

开发者常遇到的三大接口冲突场景:

pie title 你遇到过接口不匹配吗? "第三方库接口不对" : 45 "旧系统改造" : 30 "多支付渠道接入" : 25

具体场景解释

  • 第三方库接口不对 (45%)
    👉 案例:引入了一个性能优秀的日志库,但它的方法名是writeLog(),而你的系统统一调用log()
  • 旧系统改造 (30%)
    👉 案例:老系统使用XML传输数据,新模块要求JSON格式
  • 多支付渠道接入 (25%)
    👉 案例:支付宝用alipayPay(),微信用wechatPayment(),银联用unionPay(),业务层不想写if-else

🎯 一句话总结适配模式

"转换器" ------ 像胶水一样粘合两个不兼容的接口,让它们无需修改就能协作。

二、什么是适配器模式

🔥 一句话定义

适配器模式 就像代码世界的"多功能转换插头"------它通过包装不兼容的接口,让原本无法协作的两个类能够一起工作,且不修改双方原始代码


🛠️ 两种实现方式对比(含代码)

1️⃣ 类适配器(继承实现)

核心原理:让适配器同时继承目标接口和被适配者

classDiagram class Target { <> +request() } class Adaptee { +specificRequest() } class Adapter { +request() } Target <|.. Adapter Adaptee <|-- Adapter

Java代码示例

java 复制代码
// 目标接口(你的系统标准)
interface USB {
    void connect();
}

// 被适配者(第三方设备)
class TypeC {
    public void typeCConnect() {
        System.out.println("Type-C 连接成功");
    }
}

// 适配器(继承实现)
class TypeCToUSBAdapter extends TypeC implements USB {
    @Override
    public void connect() {
        super.typeCConnect();  // 调用被适配者的方法
        System.out.println("转换为USB协议通信");
    }
}

// 使用端
public class Client {
    public static void main(String[] args) {
        USB usb = new TypeCToUSBAdapter();
        usb.connect(); // 统一调用USB接口
    }
}

优点 :直接重写方法,代码简洁

缺点:Java单继承限制,无法适配多个类


2️⃣ 对象适配器(组合实现)

核心原理:在适配器中持有被适配者的对象引用

classDiagram class Target { <> +request() } class Adaptee { +specificRequest() } class Adapter { -adaptee: Adaptee +request() } Target <|.. Adapter Adapter --> Adaptee

Java代码示例

java 复制代码
class TypeCToUSBAdapter implements USB {
    private TypeC typeC;  // 组合被适配对象

    public TypeCToUSBAdapter(TypeC typeC) {
        this.typeC = typeC;
    }

    @Override
    public void connect() {
        typeC.typeCConnect(); // 委托调用
        System.out.println("转换为USB协议通信");
    }
}

// 使用端
public class Client {
    public static void main(String[] args) {
        USB usb = new TypeCToUSBAdapter(new TypeC());
        usb.connect();
    }
}

优点 :解耦更彻底,支持适配多个对象

推荐场景:Java项目首选(规避单继承问题)


💡 关键决策点:该选哪种实现?

对比维度 类适配器 对象适配器
实现方式 继承被适配类 组合被适配对象
灵活性 只能适配一个父类 可适配多个不同对象
代码侵入性 需要知道被适配类细节 仅依赖接口,更松耦合
适用场景 明确单适配源时 多适配源或未来扩展时

行业实践:对象适配器使用率超过80%(《设计模式:可复用面向对象软件的基础》统计)


🚨 避坑指南:新手常见错误

  1. 过度适配:不要为每个微小差异都创建适配器

    java 复制代码
    // 错误示范:仅仅方法名不同时不需要适配器
    class UnnecessaryAdapter implements NewInterface {
        private OldClass old;
        
        public void newMethod() {
            old.oldMethod(); // 直接改调用方更简单
        }
    }
  2. 忽略协议转换:仅调用方法不等于适配

    java 复制代码
    // 错误案例:微信支付金额单位是分,未做转换
    class WechatPayAdapter implements Payment {
        public void pay(BigDecimal amount) {
            wechatPay.pay(amount); // 应该 amount*100
        }
    }

🌰 回到支付场景(强化理解)

假设你的系统需要同时支持支付宝(金额单位元)和微信支付(金额单位分):

java 复制代码
// 对象适配器实现单位转换
class WechatPayAdapter implements Payment {
    private WechatPay wechatPay;
    
    public void pay(BigDecimal yuan) {
        BigDecimal fen = yuan.multiply(new BigDecimal(100));
        wechatPay.pay(fen.intValue()); // 转换为分
    }
}

这才是真正的适配------不仅转换接口,还要处理协议差异!

三、实战场景:支付系统对接(优化增强版)

场景痛点升级:不只是接口名称不同!

假设遇到更复杂的差异:

  • 支付宝:金额单位 ,方法alipayPay(BigDecimal yuan)
  • 微信支付:金额单位 ,方法wechatPay(int fen)
  • 银联支付:要求字符串类型金额 ,方法unionPay(String amount)

代码演进(完整商业级实现)

1️⃣ 统一定义支付接口
java 复制代码
public interface Payment {
    /**
     * 统一支付方法(单位:元)
     * @param amount 金额(元,支持小数点后两位)
     * @throws PaymentException 支付失败时抛出
     */
    void pay(BigDecimal amount) throws PaymentException;
}
2️⃣ 支付宝适配器(直接转换)
java 复制代码
public class AlipayAdapter implements Payment {
    private final Alipay alipay = new Alipay();

    @Override
    public void pay(BigDecimal yuan) {
        // 支付宝无需单位转换,直接调用
        alipay.alipayPay(yuan.setScale(2, RoundingMode.HALF_UP));
    }
}
3️⃣ 微信支付适配器(单位转换)
java 复制代码
public class WechatPayAdapter implements Payment {
    private final WechatPay wechatPay = new WechatPay();

    @Override
    public void pay(BigDecimal yuan) throws PaymentException {
        // 元转分(乘以100后取整)
        int fen = yuan.multiply(BigDecimal.valueOf(100))
                     .setScale(0, RoundingMode.HALF_UP)
                     .intValueExact();
        
        if (fen <= 0) {
            throw new PaymentException("金额不能小于0.01元");
        }
        
        wechatPay.wechatPay(fen); // 调用微信SDK
    }
}
4️⃣ 银联支付适配器(格式转换)
java 复制代码
public class UnionPayAdapter implements Payment {
    private final UnionPay unionPay = new UnionPay();

    @Override
    public void pay(BigDecimal yuan) {
        // 保留两位小数并转为字符串(如12.34元)
        String amount = yuan.setScale(2, RoundingMode.HALF_UP)
                           .toPlainString();
        unionPay.unionPay(amount + "元"); // 银联要求带单位
    }
}
5️⃣ 业务方调用(Spring Boot风格)
java 复制代码
@RestController
public class OrderController {
    // 通过@Qualifier指定支付方式
    private final Payment payment;
    
    public OrderController(@Qualifier("wechatPayAdapter") Payment payment) {
        this.payment = payment;
    }

    @PostMapping("/pay")
    public String createOrder(@RequestParam BigDecimal amount) {
        try {
            payment.pay(amount);
            return "支付成功";
        } catch (PaymentException e) {
            return "支付失败:" + e.getMessage();
        }
    }
}

🔥 关键增强点解析

  1. 精确金额处理

    • 使用BigDecimal避免浮点数精度问题
    • setScale(2)强制保留两位小数
    • RoundingMode.HALF_UP银行家舍入法
  2. 异常处理标准化

    java 复制代码
    public class PaymentException extends Exception {
        public PaymentException(String message) {
            super(message);
        }
    }
  3. Spring集成示例

    java 复制代码
    @Configuration
    public class PaymentConfig {
        @Bean("alipayAdapter")
        public Payment alipayAdapter() {
            return new AlipayAdapter();
        }
        
        @Bean("wechatPayAdapter")
        public Payment wechatPayAdapter() {
            return new WechatPayAdapter();
        }
    }
  4. 防御性编程

    • 金额最小值校验
    • intValueExact()防止溢出
    • 不可变对象(字段用final修饰)

📊 支付方式切换示意图

graph TD A[客户端] --> B[OrderController] B --> C{Payment接口} C --> D[AlipayAdapter] C --> E[WechatPayAdapter] C --> F[UnionPayAdapter] D --> G[支付宝SDK] E --> H[微信SDK] F --> I[银联SDK]

💼 商业项目经验

  1. 动态策略选择

    java 复制代码
    public class PaymentStrategy {
        private Map<PayType, Payment> strategies = new HashMap<>();
        
        public PaymentStrategy() {
            strategies.put(PayType.ALIPAY, new AlipayAdapter());
            strategies.put(PayType.WECHAT, new WechatPayAdapter());
        }
        
        public void pay(PayType type, BigDecimal amount) {
            strategies.get(type).pay(amount);
        }
    }

四、模式应用时机(含深度决策指南)


应该使用适配器模式的三大信号

1️⃣ 历史代码整合

👉 场景特征

  • 需要接入遗留系统/第三方库
  • 对方接口不符合当前系统规范
  • 典型案例
java 复制代码
// 老旧报表系统(无法修改)
class LegacyReport {
    void generate(Date from, Date to) { /* XML格式输出 */ }
}

// 适配器转换为JSON接口
class ReportAdapter implements ModernReportService {
    private LegacyReport legacyReport;
    
    public String generateJsonReport(LocalDate start, LocalDate end) {
        Date from = convertToDate(start);
        Date to = convertToDate(end);
        String xml = legacyReport.generate(from, to);
        return xmlToJson(xml); // 关键转换逻辑
    }
}
2️⃣ 多源异构系统对接

👉 场景特征

  • 同时对接多个提供相同功能但接口不同的系统
  • 需要统一对外暴露标准接口
  • 典型案例
graph LR A[你的系统] --> B[统一文件存储接口] B --> C[阿里云OSS适配器] B --> D[七牛云适配器] B --> E[Minio适配器]
3️⃣ 协议转换需求

👉 场景特征

  • 数据格式转换(XML↔JSON)
  • 单位转换(元↔分↔美元)
  • 编码转换(GBK↔UTF-8)
  • 典型案例
java 复制代码
// 温度传感器适配器(华氏度转摄氏度)
class FahrenheitSensorAdapter implements CelsiusSensor {
    private FahrenheitSensor sensor;
    
    public double getTemperature() {
        return (sensor.getFahrenheit() - 32) * 5 / 9;
    }
}

不该使用适配器的危险信号

1️⃣ 简单重命名方法
java 复制代码
// 错误示范:仅方法名不同时
class UnnecessaryAdapter {
    private OldService old;
    
    // 没有实际转换逻辑
    public void newName() {
        old.oldName(); 
    }
}

正确做法:直接修改调用方代码或使用IDE重命名重构

2️⃣ 临时性对接需求

👉 场景特征

  • 确定只使用一次的外部服务
  • 后期不会扩展其他实现
  • 解决方案:在调用方直接做简单转换
java 复制代码
// 在业务代码中直接转换
void process() {
    BigDecimal yuan = calculateAmount();
    int fen = yuan.multiply(100).intValue();
    wechatPay.pay(fen); // 无需专门写适配器
}
3️⃣ 架构层级混乱

👉 错误案例:在领域层编写适配器调用基础设施层

classDiagram direction RL class DomainService { <<领域层>> +execute() } class InfrastructureAdapter { <<基础设施层>> +call() } DomainService --> InfrastructureAdapter : 违反依赖倒置原则

正确架构

classDiagram direction LR class DomainInterface { <<领域层>> +execute()* } class InfrastructureAdapter { <<基础设施层>> +call() } DomainInterface <|.. InfrastructureAdapter

🔍 适配器 vs 其他模式(决策树)

graph TD A{需要解决接口不兼容?} A -->|是| B{需要统一多个接口?} B -->|是| C[适配器模式] B -->|否| D{需要简化复杂系统?} D -->|是| E[外观模式] D -->|否| F[直接修改接口] A -->|否| G{需要动态增强功能?} G -->|是| H[装饰器模式]

🛠️ 代码坏味道检测清单

当出现以下情况时,考虑引入适配器:

  1. 业务代码中存在大量接口转换代码

    java 复制代码
    // 坏味道示例
    void pay(String type, BigDecimal amount) {
        if ("alipay".equals(type)) {
            alipay.alipayPay(amount);
        } else if ("wechat".equals(type)) {
            wechat.wechatPayment(amount.multiply(BigDecimal.TEN));
        } // 每新增一个支付方式都要修改此处
    }
  2. 对外部系统的调用散落在多个类中

    java 复制代码
    // OrderService.java
    wechat.pay(amount.multiply(100).intValue());
    
    // RefundService.java
    wechat.refund(amount.multiply(100).intValue()); // 重复单位转换
  3. 单元测试需要大量Mock外部接口

    java 复制代码
    @Test
    void testPayment() {
        WechatPay mockWechat = mock(WechatPay.class);
        // 需要mock原始接口而不是业务接口
        service.pay(new BigDecimal("100")); 
        verify(mockWechat).wechatPayment(10000);
    }

🎯 终极决策原则

当修改调用方 的成本 > 编写适配器的成本时,才使用适配器模式

  1. 配置化扩展

    application.yml中配置启用哪些支付方式:

    yaml 复制代码
    payment:
      enabled:
        - alipay
        - wechat
  2. 适配器+工厂模式结合

    java 复制代码
    public class PaymentFactory {
        public static Payment create(String type) {
            switch (type.toLowerCase()) {
                case "alipay": return new AlipayAdapter();
                case "wechat": return new WechatPayAdapter();
                default: throw new IllegalArgumentException("不支持的支付类型");
            }
        }
    }

五、Spring框架中的经典案例(源码级解析)


🔥 场景痛点:Controller的多样性

Spring MVC需要处理多种类型的Controller:

  • 基于@Controller注解的处理器
  • 实现Servlet接口的传统方式
  • 实现Controller接口的旧版Spring MVC写法
  • 其他自定义处理器(如gRPC、WebSocket)

问题 :如何让DispatcherServlet用统一的方式调用这些不同结构的Controller?


💡 解决方案:HandlerAdapter

Spring通过适配器模式将各种Controller统一成标准处理流程:

classDiagram class HandlerAdapter { <> +supports(Object handler) boolean +handle(HttpServletRequest request, HttpServletResponse response, Object handler) ModelAndView } class RequestMappingHandlerAdapter { +handle() ModelAndView +supports() boolean -invokeHandlerMethod() } class SimpleControllerHandlerAdapter { +handle() ModelAndView +supports() boolean } HandlerAdapter <|.. RequestMappingHandlerAdapter HandlerAdapter <|.. SimpleControllerHandlerAdapter

🌰 代码级演示(模拟简化版)

1. 定义HandlerAdapter接口
java 复制代码
public interface HandlerAdapter {
    boolean supports(Object handler);
    
    ModelAndView handle(HttpServletRequest request, 
                        HttpServletResponse response, 
                        Object handler) throws Exception;
}
2. 实现注解控制器适配器
java 复制代码
public class AnnotationHandlerAdapter implements HandlerAdapter {
    
    @Override
    public boolean supports(Object handler) {
        return handler instanceof MyAnnotationController;
    }

    @Override
    public ModelAndView handle(HttpServletRequest request, 
                               HttpServletResponse response,
                               Object handler) {
        MyAnnotationController controller = (MyAnnotationController) handler;
        // 解析@RequestMapping等注解
        String result = controller.processRequest(request);
        return new ModelAndView(result);
    }
}
3. 实现Servlet适配器
java 复制代码
public class ServletHandlerAdapter implements HandlerAdapter {
    
    @Override
    public boolean supports(Object handler) {
        return handler instanceof Servlet;
    }

    @Override
    public ModelAndView handle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler) throws Exception {
        Servlet servlet = (Servlet) handler;
        servlet.service(request, response); // 直接调用Servlet原生方法
        return null; // Servlet自己处理响应
    }
}

🔍 源码解析:RequestMappingHandlerAdapter

Spring实际源码中的关键设计:

java 复制代码
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter {
    
    // 核心处理方法
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response,
            HandlerMethod handlerMethod) throws Exception {
        
        // 1. 参数解析(@RequestParam等)
        Object[] args = getMethodArgumentValues(request, response, handlerMethod);
        
        // 2. 调用Controller方法
        Object returnValue = invokeForRequest(request, response, handlerMethod, args);
        
        // 3. 返回值处理(@ResponseBody等)
        return handleReturnValue(returnValue, handlerMethod, request, response);
    }
    
    // 判断是否支持该Handler
    public boolean supports(Object handler) {
        return (handler instanceof HandlerMethod);
    }
}

关键流程

  1. 参数解析 :通过HandlerMethodArgumentResolver处理各种参数注解
  2. 方法调用:通过反射执行Controller方法
  3. 返回值处理 :使用HandlerMethodReturnValueHandler处理@ResponseBody等

🛠️ Spring MVC处理流程(适配器视角)

sequenceDiagram participant DispatcherServlet participant HandlerAdapter participant Controller DispatcherServlet ->> HandlerAdapter: handle() activate HandlerAdapter HandlerAdapter ->> Controller: 解析参数、调用方法 Controller -->> HandlerAdapter: 返回结果 HandlerAdapter ->> HandlerAdapter: 转换返回值 HandlerAdapter -->> DispatcherServlet: ModelAndView deactivate HandlerAdapter

💼 实际项目中的应用

场景:统一处理自定义协议控制器

假设需要支持通过@ProtobufController注解处理Protobuf格式请求:

java 复制代码
// 1. 定义自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ProtobufController {
}

// 2. 实现适配器
public class ProtobufHandlerAdapter implements HandlerAdapter {
    
    @Override
    public boolean supports(Object handler) {
        return handler.getClass().isAnnotationPresent(ProtobufController.class);
    }

    @Override
    public ModelAndView handle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler) throws Exception {
        // Protobuf反序列化逻辑
        byte[] data = request.getInputStream().readAllBytes();
        Message requestObj = parseProtobuf(data);
        
        // 反射调用处理方法
        Method method = findHandlerMethod(handler);
        Object result = method.invoke(handler, requestObj);
        
        // Protobuf序列化响应
        writeProtobuf(response, result);
        return null;
    }
}

🎯 设计启示

  1. 开闭原则:新增Controller类型只需添加适配器,无需修改DispatcherServlet
  2. 单一职责:每个适配器只负责一种处理逻辑
  3. 协议转换:适配器不仅做接口适配,还处理数据格式转换
pie title Spring MVC中适配器的作用分布 "接口适配" : 40 "参数解析" : 30 "返回值处理" : 20 "异常转换" : 10

六、动手作业:日志框架适配(商业级实现)

需求升级:完整日志门面实现

  • 支持主流日志框架:Log4j 1.x/2.x、Logback
  • 统一日志接口功能:
    • 支持不同日志级别(DEBUG/INFO/WARN/ERROR)
    • 支持占位符({}替换)
    • 支持异常堆栈打印

代码实现(生产级规范)

1️⃣ 统一定义日志接口
java 复制代码
public interface Logger {
    void debug(String format, Object... args);
    void info(String format, Object... args);
    void warn(String format, Object... args);
    void error(String format, Object... args);
    void error(String msg, Throwable t);
}
2️⃣ Log4j 1.x适配器
java 复制代码
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public class Log4jAdapter implements Logger {
    private final Logger log;

    public Log4jAdapter(String name) {
        this.log = Logger.getLogger(name);
    }

    @Override
    public void debug(String format, Object... args) {
        if (log.isDebugEnabled()) {
            log.debug(formatMessage(format, args));
        }
    }

    @Override
    public void error(String msg, Throwable t) {
        log.error(msg, t);
    }

    // 其他级别方法实现类似...
    
    private String formatMessage(String format, Object... args) {
        return String.format(format.replace("{}", "%s"), args);
    }
}
3️⃣ Logback适配器
java 复制代码
import ch.qos.logback.classic.Logger;

public class LogbackAdapter implements Logger {
    private final Logger log;

    public LogbackAdapter(org.slf4j.Logger logger) {
        this.log = (Logger) logger;
    }

    @Override
    public void info(String format, Object... args) {
        if (log.isInfoEnabled()) {
            log.info(format, args);
        }
    }

    // 其他方法实现类似Log4jAdapter...
}
4️⃣ 日志工厂(自动检测环境)
java 复制代码
public class LoggerFactory {
    private static final String LOG_TYPE = detectLogFramework();

    public static Logger getLogger(Class<?> clazz) {
        String name = clazz.getName();
        switch (LOG_TYPE) {
            case "log4j":
                return new Log4jAdapter(name);
            case "logback":
                return new LogbackAdapter(org.slf4j.LoggerFactory.getLogger(clazz));
            default:
                throw new IllegalStateException("未检测到支持的日志框架");
        }
    }

    private static String detectLogFramework() {
        try {
            Class.forName("org.apache.log4j.Logger");
            return "log4j";
        } catch (ClassNotFoundException e1) {
            try {
                Class.forName("ch.qos.logback.classic.Logger");
                return "logback";
            } catch (ClassNotFoundException e2) {
                throw new RuntimeException("请添加日志框架依赖");
            }
        }
    }
}
5️⃣ 业务使用示例
java 复制代码
public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);
    
    public void createOrder() {
        log.info("开始创建订单,用户ID:{}", 12345);
        try {
            // 业务逻辑
        } catch (Exception e) {
            log.error("订单创建失败", e);
        }
    }
}

🔥 关键实现细节

  1. 性能优化

    • 日志级别判断(isDebugEnabled())避免不必要的字符串拼接
    java 复制代码
    // 错误写法(即使不输出也会拼接字符串)
    log.debug("User info: " + user); 
    
    // 正确写法
    if (log.isDebugEnabled()) {
        log.debug("User info: {}", user.toString());
    }
  2. 占位符统一处理

    • {}转换为String.format%s
    java 复制代码
    String message = "用户:{} 余额:{}";
    Object[] args = {"张三", 100.50};
    // 格式化为:用户:张三 余额:100.50
  3. 异常堆栈处理

    • 单独提供带Throwable参数的error方法
    java 复制代码
    // 正确保留堆栈信息
    catch (Exception e) {
        log.error("支付失败", e); // 而不仅是e.getMessage()
    }

📦 Maven依赖配置

xml 复制代码
<!-- Log4j 1.x适配 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

<!-- Logback适配 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.6</version>
</dependency>

🌍 架构图:日志门面工作原理

graph TD A[业务代码] -->|调用| B[统一日志接口] B --> C{日志适配器} C --> D[Log4j实现] C --> E[Logback实现] C --> F[其他实现] D --> G[写入文件] E --> H[输出到控制台] F --> I[发送到日志服务器]

🚀 高级扩展方向

  1. 动态切换日志实现

    java 复制代码
    // 运行时指定日志框架
    System.setProperty("logging.framework", "logback");
  2. 配置热更新

    java 复制代码
    public class Log4jAdapter implements Logger {
        private volatile LogLevel currentLevel;
        
        public void setLevel(LogLevel level) {
            this.currentLevel = level;
            // 更新底层Log4j配置
        }
    }
  3. 日志链路追踪

    java 复制代码
    public void info(String format, Object... args) {
        String traceId = MDC.get("traceId");
        String newFormat = "[traceId:{}] " + format;
        Object[] newArgs = ArrayUtils.addFirst(args, traceId);
        log.info(newFormat, newArgs);
    }
相关推荐
追逐时光者1 小时前
分享一个纯净无广、原版操作系统、开发人员工具、服务器等资源免费下载的网站
后端·github
JavaPub-rodert2 小时前
golang 的 goroutine 和 channel
开发语言·后端·golang
ivygeek3 小时前
MCP:基于 Spring AI Mcp 实现 webmvc/webflux sse Mcp Server
spring boot·后端·mcp
GoGeekBaird4 小时前
69天探索操作系统-第54天:嵌入式操作系统内核设计 - 最小内核实现
后端·操作系统
鱼樱前端4 小时前
Java Jdbc相关知识点汇总
java·后端
canonical_entropy5 小时前
NopReport示例-动态Sheet和动态列
java·后端·excel
kkk哥5 小时前
基于springboot的母婴商城系统(018)
java·spring boot·后端
Asthenia04126 小时前
面试复盘:关于 Redis 如何实现分布式锁
后端
Asthenia04126 小时前
如何修改 MySQL 的数据库隔离级别:命令global、session/my.cnf中修改
后端