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

324 阅读12分钟

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

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

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

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

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


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

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

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

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


3️⃣ 技术世界的接口冲突

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

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

具体场景解释

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

🎯 一句话总结适配模式

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

二、什么是适配器模式

🔥 一句话定义

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


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

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

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

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

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 {
        <<interface>>
        +request()
    }
    class Adaptee {
        +specificRequest()
    }
    class Adapter {
        -adaptee: Adaptee
        +request()
    }
    Target <|.. Adapter
    Adapter --> Adaptee

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. 过度适配:不要为每个微小差异都创建适配器

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

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

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

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

// 对象适配器实现单位转换
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️⃣ 统一定义支付接口
public interface Payment {
    /**
     * 统一支付方法(单位:元)
     * @param amount 金额(元,支持小数点后两位)
     * @throws PaymentException 支付失败时抛出
     */
    void pay(BigDecimal amount) throws PaymentException;
}
2️⃣ 支付宝适配器(直接转换)
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️⃣ 微信支付适配器(单位转换)
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️⃣ 银联支付适配器(格式转换)
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风格)
@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. 异常处理标准化

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

    @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. 动态策略选择
    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️⃣ 历史代码整合

👉 场景特征

  • 需要接入遗留系统/第三方库
  • 对方接口不符合当前系统规范
  • 典型案例
// 老旧报表系统(无法修改)
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)
  • 典型案例
// 温度传感器适配器(华氏度转摄氏度)
class FahrenheitSensorAdapter implements CelsiusSensor {
    private FahrenheitSensor sensor;
    
    public double getTemperature() {
        return (sensor.getFahrenheit() - 32) * 5 / 9;
    }
}

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

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

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

2️⃣ 临时性对接需求

👉 场景特征

  • 确定只使用一次的外部服务
  • 后期不会扩展其他实现
  • 解决方案:在调用方直接做简单转换
// 在业务代码中直接转换
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. 业务代码中存在大量接口转换代码

    // 坏味道示例
    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. 对外部系统的调用散落在多个类中

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

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

🎯 终极决策原则

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

  1. 配置化扩展
    application.yml中配置启用哪些支付方式:

    payment:
      enabled:
        - alipay
        - wechat
    
  2. 适配器+工厂模式结合

    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 {
        <<interface>>
        +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接口
public interface HandlerAdapter {
    boolean supports(Object handler);
    
    ModelAndView handle(HttpServletRequest request, 
                        HttpServletResponse response, 
                        Object handler) throws Exception;
}
2. 实现注解控制器适配器
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适配器
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实际源码中的关键设计:

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格式请求:

// 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️⃣ 统一定义日志接口
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适配器
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适配器
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️⃣ 日志工厂(自动检测环境)
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️⃣ 业务使用示例
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())避免不必要的字符串拼接
    // 错误写法(即使不输出也会拼接字符串)
    log.debug("User info: " + user); 
    
    // 正确写法
    if (log.isDebugEnabled()) {
        log.debug("User info: {}", user.toString());
    }
    
  2. 占位符统一处理

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

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

📦 Maven依赖配置

<!-- 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. 动态切换日志实现

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

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

    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);
    }