适配器应采用对象组合而非继承,持有oldservice实例,统一处理异常转换、空值、类型映射、线程上下文及生命周期;spring中需显式声明老服务bean并规范命名;单元测试须覆盖真实毛刺场景。

Java里怎么写一个不改老代码的适配器
直接继承老接口再实现新逻辑,往往行不通——老类可能被 final 修饰,或者你根本没源码。这时候得用对象组合,而不是继承。核心就一条:让新系统能调用 OldService.doLegacyWork(),但对外暴露的是 NewProcessor.process() 这样的方法签名。
常见错误是把适配器写成“包装+转发”就完事,结果漏掉异常转换、空值处理或线程上下文传递。比如老接口抛 LegacyException,新协议要求统一返回 Result<t></t>,不转就会崩在上层。
- 优先用「对象适配器」:持有
OldService实例,而非继承它 - 构造器里校验依赖是否为 null,别等运行时才
NullPointerException - 如果老接口有状态(比如连接池、缓存),注意适配器是否要同步生命周期(如实现
AutoCloseable)
适配器里怎么处理参数类型不匹配
老接口收 String id,新系统传的是 UUID;老方法返回 Map<string object></string>,新契约要 ResponseDTO——这类转换不能堆在业务层,必须封进适配器内部。
容易踩的坑是硬编码字段名或做浅拷贝。比如把 oldMap.get("user_name") 直接塞进 dto.setName(),一旦老字段改名或大小写变化,立刻失效;又或者用 new HashMap(oldMap),结果老 map 后续被修改,dto 也跟着变。
立即学习“Java免费学习笔记(深入)”;
- 用静态工具方法做字段映射,比如
LegacyMapper.toDto(oldMap),别散落在多个适配器里 - 对不可变对象(如
LocalDateTime),别直接赋值,要调用toInstant()或atZone()转时区 - 如果老接口参数是数组,新接口是集合,用
Arrays.asList()前先确认是否允许修改——否则用new ArrayList(Arrays.asList(...))
Spring环境下怎么让适配器自动注入且不冲突
当老服务和新服务都注册为 Spring Bean,直接 @Autowired 会报 NoUniqueBeanDefinitionException。不是加 @Qualifier 就完事——更关键的是命名规范和作用域控制。
典型翻车现场:给适配器类加了 @Service,但忘了它依赖的 OldService 是第三方 jar 里的类,没被 Spring 扫描到,结果启动就 UnsatisfiedDependencyException。
- 老服务实例必须显式声明为 Bean,比如在配置类里写
@Bean @Primary OldService legacyService() - 适配器类用
@Component("newProcessorAdapter"),避免默认名和原服务冲突 - 如果老服务是单例但有线程不安全操作,适配器别标
@Scope("prototype")——应封装同步逻辑,而非靠新建实例规避问题
为什么适配器单元测试总漏掉边界情况
测通路没问题,一上线就遇到老接口返回 null、空集合、超长字符串或时区为 UTC+0 的时间戳。根本原因是测试只 mock 了 happy path,没覆盖 OldService 真实行为的毛刺点。
尤其要注意老系统常有的“隐式约定”:比如 getItems() 返回 null 表示无数据,而不是空 list;又或者 status 字段只用 "0" 和 "1",但从不校验其他值——适配器必须主动防御。
- 用真实
OldService的轻量级实现(哪怕只是内存 map 模拟)代替纯 mock - 测试用例至少覆盖:null 返回值、空集合、字段缺失、非法枚举值、时区偏移非零的时间对象
- 日志里打出来的老接口输入/输出,别只留 debug 级别——在适配器关键路径加
log.warn("legacy response null for request: {}", reqId)
适配器最麻烦的从来不是结构,而是老系统那些没文档、没注释、靠口耳相传的隐性规则。多看几眼生产日志里的实际响应,比读十遍接口定义有用。










