我把祖传Java项目重构后,接口响应从3s砍到了200ms,只改了这几行代码

前言:祖传老项目的性能噩梦

做 Java 开发的程序员,大概率都接手过祖传老旧项目。这类项目普遍有几个典型特征:代码混乱无规范、嵌套冗余严重、数据库操作野蛮粗暴、无缓存、无异步、无性能监控,靠着"能跑就行"的准则苟活数年,随着业务数据量增长,性能问题彻底爆发。

我本次接手的是公司上线7年的老后台管理系统,核心的订单列表查询接口 ,在测试环境少量数据下表现正常,一旦切换到生产环境(数据量超80万条订单、关联用户、商品、物流、日志十余张表),接口响应直接稳定在3秒左右

3秒的响应速度,放在当下的互联网产品中完全是致命短板:前端页面加载卡顿、用户投诉反馈、运营后台操作卡顿,高峰期多用户并发访问时,甚至会出现接口超时、Tomcat线程阻塞、系统短暂瘫痪的问题。

最让人头疼的是,这是祖传遗留项目,前人代码写得极其冗余,很多逻辑层层嵌套,没有人敢大规模重构,担心改出 BUG 影响线上业务。团队原本计划投入两周时间专项优化,而我通过精准定位瓶颈、针对性极简代码改造 ,仅修改了少量核心代码,就将接口平均响应时间从3000ms 压缩至 200ms,性能提升15倍,且全程零业务BUG、零线上事故。

很多人觉得 性能优化 需要大改架构、重构整体代码、引入复杂中间件,但本次实战我将证明:绝大多数老旧Java项目的接口性能卡顿,根本不是架构问题,而是低级代码陋习、不合理的数据库操作、资源浪费导致的,只需修改少量核心代码,就能实现性能质的飞跃

本文我将完整复盘全流程:从问题现象、瓶颈排查、根因分析,到逐点代码优化、原理讲解、压测对比、线上落地,所有优化点均为可直接复用的实战技巧,适合所有Java开发者解决老旧项目性能问题。

一、项目现状与性能问题复盘

1.1 项目基础信息

本次优化的祖传项目基础技术栈老旧但主流:Spring Boot 2.1、MyBatis 3.4、MySQL 5.7、Tomcat 8.5,无Redis缓存、无异步线程池、无数据库索引优化、无SQL拦截监控。

核心慢接口:/api/order/list 订单分页查询接口,接口功能包含:分页查询订单基础信息、关联查询用户信息、商品信息、物流信息、订单操作日志、退款记录,是后台运营最常用的核心接口,日均调用量超5万次。

1.2 优化前性能数据(线上真实监控)

  • 平均响应时间:3120ms

  • 最大响应时间:5800ms(高峰期)

  • 超时率(>3s):18.7%

  • 并发能力:单接口最大支持15并发,超过后线程阻塞

  • 数据库CPU占用:高峰期稳定85%以上

1.3 初期误区:盲目加机器、调参数

在我接手之前,团队为了解决卡顿问题,做过很多无效操作:升级服务器配置、调高Tomcat最大线程数、增大MySQL连接池数量,但所有操作都治标不治本。

服务器从4核8G升级为8核16G,响应速度仅缩短200ms;Tomcat线程数从200调至500,反而出现大量线程等待、锁竞争问题,超时率更高。

这也印证了一个核心观点:代码层面的低级性能问题,靠硬件扩容、参数调优完全无法根治,只会浪费服务器资源。想要彻底优化,必须精准找到性能瓶颈,从代码、SQL逻辑根源解决问题。

二、精准排查:定位3秒卡顿的核心罪魁祸首

性能优化的核心前提:不盲目改代码,先精准定位瓶颈。很多开发者优化效率低,就是因为凭感觉改代码,浪费大量时间却没有效果。本次我通过三个维度快速锁定卡顿根源。

2.1 接口全链路耗时拆分

我通过Spring AOP切面打印接口全链路耗时,将接口执行流程拆解为:参数校验、数据库查询、数据封装、关联数据查询、结果序列化、返回响应六个环节,精准统计每一步耗时。

耗时统计结果:

  • 参数校验、序列化、响应返回:总耗时<100ms,无性能问题

  • 订单主表分页查询:耗时400ms

  • 循环关联查询用户、商品、物流、日志数据:耗时2400ms

  • 数据遍历封装、重复计算:耗时220ms

结论清晰:90%的耗时都浪费在循环查库、重复IO、无效逻辑执行上,这也是祖传项目最典型的性能痛点。

2.2 慢SQL日志分析

开启MySQL慢查询日志后,发现该接口单次请求会触发数十次数据库查询,核心问题为:

  1. 分页查询订单列表后,循环遍历每条订单,单独查询关联数据(经典N+1查询问题)

  2. 关联查询无索引,单条关联查询耗时50-100ms,循环后耗时爆炸

  3. 查询SQL使用select *,查询大量无用字段,增加IO传输压力

  4. 无缓存机制,每次请求都全量查库,重复查询相同静态数据

2.3 代码层面核心问题汇总

梳理完执行链路与SQL日志后,我总结出祖传代码的4个致命性能BUG,也是本次优化的核心切入点,所有问题均只需少量代码改造即可修复:

  1. 严重的数据库N+1查询,循环单条查库,频繁创建销毁数据库连接

  2. 无本地缓存、无接口缓存,静态关联数据每次都重复查库

  3. 接口同步执行所有非核心逻辑,阻塞主流程响应

  4. 代码存在大量重复计算、无效遍历、资源未复用问题

三、逐点极简代码重构:万字实战核心优化点

接下来进入核心实战环节,我将逐一对上述问题进行代码重构,所有优化均为少量代码修改,无大规模业务重构、无架构升级,每一处优化都附带【优化前问题代码+优化后代码+原理讲解+耗时对比】,可直接复用。

3.1 优化一:根治N+1循环查库(最大性能瓶颈,节省2400ms)

这是本次优化收益最大的改造点,也是导致接口3秒卡顿的核心原因。祖传代码采用最原始的循环单条查库方式,分页查出10条订单,就会循环执行10次用户查询、10次商品查询、10次物流查询,单次请求额外触发40次无效SQL查询。

优化前:问题代码(典型N+1坑)

接口核心业务逻辑代码,分页查询订单后,循环遍历逐条查询关联数据:

bash 复制代码
/**
 * 祖传卡顿代码:订单列表查询
 * 问题:循环单条查库,N+1查询灾难
 */
@Override
public PageResult<OrderVO> getOrderList(OrderQueryDTO queryDTO) {
    // 1. 分页查询订单主表数据(1次SQL)
    Page<Order> orderPage = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
    List<Order> orderList = orderMapper.selectPage(orderPage, queryDTO).getRecords();
    
    List<OrderVO> voList = new ArrayList<>();
    // 2. 循环遍历订单,逐条查询关联数据(N次SQL)
    for (Order order : orderList) {
        OrderVO vo = new OrderVO();
        BeanUtils.copyProperties(order, vo);
        
        // 循环单条查询用户信息
        User user = userMapper.selectById(order.getUserId());
        vo.setUserName(user.getUserName());
        vo.setUserPhone(user.getUserPhone());
        
        // 循环单条查询商品信息
        Goods goods = goodsMapper.selectById(order.getGoodsId());
        vo.setGoodsName(goods.getGoodsName());
        vo.setGoodsPrice(goods.getGoodsPrice());
        
        // 循环单条查询物流信息
        Logistics logistics = logisticsMapper.selectByOrderId(order.getId());
        vo.setLogisticsStatus(logistics.getStatus());
        vo.setLogisticsNo(logistics.getLogisticsNo());
        
        // 循环单条查询订单日志
        List<OrderLog> logList = orderLogMapper.selectByOrderId(order.getId());
        vo.setOrderLogList(logList);
        
        voList.add(vo);
    }
    return new PageResult<>(orderPage.getTotal(), voList);
}

问题分析 :假设分页10条数据,该方法会执行 1次主查询 + 10次用户查询 + 10次商品查询 + 10次物流查询 + 10次日志查询 = 41次SQL请求,大量数据库IO交互,极大拉长响应时间。

数据库连接的创建、销毁、网络往返是非常耗时的操作,高频小批量查询的耗时,远大于一次批量查询的耗时,这也是接口耗时爆炸的核心根源。

优化后:批量查询替代循环查询(仅修改20行代码)

核心优化思想:先批量收集所有关联ID,一次性批量查询所有数据,内存遍历匹配封装,彻底消灭N+1查询。无论分页10条还是50条,关联查询仅执行4次SQL,耗时大幅降低。

bash 复制代码
/**
 * 优化后代码:根治N+1查询,性能暴涨
 * 核心:批量查库 + 内存匹配替代循环查库
 */
@Override
public PageResult<OrderVO> getOrderList(OrderQueryDTO queryDTO) {
    // 1. 分页查询订单主表数据(1次SQL)
    Page<Order> orderPage = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
    List<Order> orderList = orderMapper.selectPage(orderPage, queryDTO).getRecords();
    if (CollectionUtils.isEmpty(orderList)) {
        return new PageResult<>(0, new ArrayList<>());
    }

    // 2. 批量收集所有关联ID(内存操作,无IO耗时)
    Set<Long> userIdSet = orderList.stream().map(Order::getUserId).collect(Collectors.toSet());
    Set<Long> goodsIdSet = orderList.stream().map(Order::getGoodsId).collect(Collectors.toSet());
    List<Long> orderIdList = orderList.stream().map(Order::getId).collect(Collectors.toList());

    // 3. 批量查询关联数据(仅4次SQL,固定次数,与分页条数无关)
    Map<Long, User> userMap = userMapper.selectBatchIds(userIdSet).stream()
            .collect(Collectors.toMap(User::getId, Function.identity(), (k1, k2) -> k1));
    Map<Long, Goods> goodsMap = goodsMapper.selectBatchIds(goodsIdSet).stream()
            .collect(Collectors.toMap(Goods::getId, Function.identity(), (k1, k2) -> k1));
    Map<Long, Logistics> logisticsMap = logisticsMapper.selectBatchOrderIds(orderIdList).stream()
            .collect(Collectors.toMap(Logistics::getOrderId, Function.identity(), (k1, k2) -> k1));
    Map<Long, List<OrderLog>> logMap = orderLogMapper.selectBatchOrderIds(orderIdList).stream()
            .collect(Collectors.groupingBy(OrderLog::getOrderId));

    // 4. 内存遍历封装数据(无数据库IO,毫秒级完成)
    List<OrderVO> voList = new ArrayList<>();
    for (Order order : orderList) {
        OrderVO vo = new OrderVO();
        BeanUtils.copyProperties(order, vo);
        // 内存Map匹配,O(1)查询,无IO耗时
        User user = userMap.getOrDefault(order.getUserId(), new User());
        Goods goods = goodsMap.getOrDefault(order.getGoodsId(), new Goods());
        Logistics logistics = logisticsMap.getOrDefault(order.getId(), new Logistics());
        List<OrderLog> logList = logMap.getOrDefault(order.getId(), new ArrayList<>());

        vo.setUserName(user.getUserName());
        vo.setUserPhone(user.getUserPhone());
        vo.setGoodsName(goods.getGoodsName());
        vo.setGoodsPrice(goods.getGoodsPrice());
        vo.setLogisticsStatus(logistics.getStatus());
        vo.setLogisticsNo(logistics.getLogisticsNo());
        vo.setOrderLogList(logList);
        voList.add(vo);
    }
    return new PageResult<>(orderPage.getTotal(), voList);
}
优化效果与原理说明

优化原理:将原来的「循环多次数据库IO」改为「一次批量IO + 内存快速匹配」,数据库IO次数从41次/请求,压缩至5次/请求,彻底杜绝高频网络往返耗时。

耗时对比 :该优化直接将接口耗时从3120ms 降至 900ms,单次优化节省2200ms,解决70%以上的性能问题。

补充细节:代码中使用Set去重ID、Map存储关联数据,规避重复查询,同时增加空值判断,避免空指针异常,保证业务稳定性。

3.2 优化二:精准SQL优化,杜绝无效字段查询(节省300ms)

祖传项目的SQL全部采用select * from 表名的写法,这是新手最容易犯、也是最影响数据库性能的陋习。数据表中存在大量大字段(备注、详情、富文本内容),分页查询时完全不需要展示,却每次都全量查询,增加磁盘IO、网络传输、内存占用。

优化前:问题SQL
bash 复制代码
-- 订单主表查询SQL
select * from t_order where delete_status = 0 order by create_time desc
-- 关联用户查询SQL
select * from t_user where id = #{userId}

问题:查询数十个无用字段,其中包含text、longtext大字段,数据序列化、传输耗时极高。

优化后:指定精准字段查询
bash 复制代码
-- 订单分页精准查询,只查业务需要的字段
select id, order_no, user_id, goods_id, order_amount, pay_status, create_time from t_order where delete_status = 0 order by create_time desc

-- 用户关联精准查询,仅查询展示所需字段
select id, user_name, user_phone from t_user where id in #{userIdSet}
配套索引优化(1行SQL改造)

批量查询依赖ID、order_id字段,老表无索引,导致批量查询为全表扫描,我仅新增两条普通索引,无需改代码:

bash 复制代码
-- 物流表订单ID索引
create index idx_order_id on t_logistics(order_id);
-- 订单日志表订单ID索引
create index idx_order_id on t_order_log(order_id);
优化效果

精准字段查询减少数据传输体积60%以上,索引优化将批量查询耗时从单次80ms降至10ms以内,整体接口耗时从900ms 降至 600ms,节省300ms。

3.3 优化三:本地缓存重构,消灭重复静态数据查询(节省250ms)

接口中查询的商品名称、商品价格、用户基础信息等数据,更新频率极低,属于静态热数据,但老代码每次请求都重复查库,百万级请求下造成极大的数据库压力。

我引入高性能本地缓存Caffeine,仅需少量代码改造,实现静态数据缓存,彻底消灭重复查库。相较于Redis分布式缓存,本地缓存无需网络IO,性能更高,适合单机静态数据缓存场景。

第一步:引入Maven依赖
bash 复制代码
<!-- 高性能本地缓存Caffeine -->
<dependency>
    <groupId>com.github.benmanes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.3</version>
</dependency>
第二步:编写缓存工具类(通用可复用)
bash 复制代码
/**
 * 高性能本地缓存工具类
 * 过期时间10分钟,最大缓存10000条,自动淘汰冷门数据
 */
@Service
public class LocalCacheService {
    private final Cache<String, Object> caffeineCache;

    public LocalCacheService() {
        this.caffeineCache = Caffeine.newBuilder()
                .maximumSize(10000) // 最大缓存数量
                .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期
                .recordStats() // 统计缓存命中率
                .build();
    }

    // 缓存获取
    public <T> T get(String key, Class<T> clazz) {
        Object value = caffeineCache.getIfPresent(key);
        return value == null ? null : clazz.cast(value);
    }

    // 缓存写入
    public void set(String key, Object value) {
        caffeineCache.put(key, value);
    }

    // 缓存删除
    public void delete(String key) {
        caffeineCache.invalidate(key);
    }
}
第三步:业务代码接入缓存优化
bash 复制代码
// 优化后的商品数据查询逻辑(缓存优先)
private Goods getGoodsInfo(Long goodsId) {
    String cacheKey = "goods:" + goodsId;
    // 1. 先查缓存,命中直接返回
    Goods cacheGoods = localCacheService.get(cacheKey, Goods.class);
    if (cacheGoods != null) {
        return cacheGoods;
    }
    // 2. 缓存未命中,查库并写入缓存
    Goods goods = goodsMapper.selectById(goodsId);
    if (goods != null) {
        localCacheService.set(cacheKey, goods);
    }
    return goods;
}
优化效果

静态数据查询命中率超95%,彻底消灭高频重复查库,接口耗时从600ms 降至 350ms,同时大幅降低数据库CPU压力。

缓存过期时间设置为10分钟,兼顾数据实时性与性能,商品、用户信息更新后,10分钟自动刷新,无需手动维护缓存,零维护成本。

3.4 优化四:非核心逻辑异步化,释放主流程阻塞(节省100ms)

老代码将所有逻辑同步执行,包含大量不影响接口返回的非核心逻辑:操作日志记录、用户访问统计、接口调用日志上报、积分更新等。这些逻辑无需同步执行,阻塞主流程响应,完全可以异步化处理。

本次使用JDK8自带的CompletableFuture实现异步化,无需引入额外中间件,仅修改数行代码,彻底解放主流程。同时自定义线程池,规避默认线程池风险。

第一步:自定义业务线程池(杜绝并行流坑)
bash 复制代码
/**
 * 自定义业务异步线程池
 * 核心:避免使用默认ForkJoinPool,防止业务线程阻塞
 */
@Configuration
public class ThreadPoolConfig {
    @Bean("businessThreadPool")
    public ExecutorService businessThreadPool() {
        return new ThreadPoolExecutor(
                10,
                50,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000),
                new ThreadFactoryBuilder().setNamePrefix("business-async-thread-").build(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }
}
第二步:非核心逻辑异步改造
bash 复制代码
// 注入自定义线程池
@Resource(name = "businessThreadPool")
private ExecutorService businessThreadPool;

@Override
public PageResult<OrderVO> getOrderList(OrderQueryDTO queryDTO) {
    // 核心主流程:分页查询、数据封装(同步执行)
    PageResult<OrderVO> result = getOrderPageData(queryDTO);
    
    // 非核心逻辑:异步执行,不阻塞主响应
    CompletableFuture.runAsync(() -> {
        try {
            // 记录用户查询日志
            saveUserQueryLog(queryDTO);
            // 统计接口调用次数
            countApiInvoke();
            // 更新用户活跃时间
            updateUserActiveTime(queryDTO.getUserId());
        } catch (Exception e) {
            // 异步异常不影响主业务
            log.error("订单列表异步日志处理失败", e);
        }
    }, businessThreadPool);

    return result;
}
优化效果

剥离所有非核心阻塞逻辑,主流程无需等待日志、统计等逻辑执行完成,接口响应速度进一步提升,耗时从350ms 降至 250ms。同时异步线程池隔离业务,避免异步逻辑异常影响核心接口。

3.5 优化五:代码细节精简,消灭无效性能损耗(节省50ms)

除了核心大问题,祖传代码还存在大量细节陋习,积少成多造成性能损耗,我通过少量代码修改彻底优化:

细节1:复用Bean属性拷贝,减少重复创建对象

老代码循环内频繁创建工具类对象、重复拷贝属性,优化后统一复用实例:

bash 复制代码
// 优化前:循环内重复创建对象
for (Order order : orderList) {
    OrderVO vo = new OrderVO();
    BeanUtils.copyProperties(order, vo);
}

// 优化后:无冗余创建,同时规避BeanUtils性能问题
// 补充:手动赋值替代BeanUtils,性能提升30%,避免反射耗时
细节2:启用接口Gzip压缩

在application.yml中新增3行配置,开启响应数据压缩,减少网络传输耗时:

bash 复制代码
server:
  compression:
    enabled: true # 开启Gzip压缩
    mime-types: application/json,text/html # 压缩类型
    min-response-size: 1024 # 最小压缩大小
细节3:杜绝重复计算

老代码循环内重复获取系统时间、重复计算字符串,优化后提前全局计算:

bash 复制代码
// 优化前:循环内重复计算
for (Order order : orderList) {
    String token = MD5Util.md5(order.getId() + System.currentTimeMillis());
}

// 优化后:提前计算,复用变量
long currentTime = System.currentTimeMillis();
for (Order order : orderList) {
    String token = MD5Util.md5(order.getId() + currentTime);
}
优化效果

细节优化累计节省50ms耗时,接口最终稳定在200ms左右,完美达成优化目标。

四、全维度压测验证:优化前后数据对比

所有代码改造完成后,我通过JMeter进行1000并发、持续10分钟的压力测试,对比优化前后核心性能指标,数据提升极其夸张。

4.1 核心性能数据对比表

性能指标 优化前 优化后 提升幅度
平均响应时间 3120ms 200ms 15倍提升
最大响应时间 5800ms 380ms 15倍提升
接口超时率 18.7% 0% 彻底解决超时问题
每秒吞吐量TPS 32 486 15倍提升
数据库CPU占用 85%+ 25%左右 大幅降低数据库压力
并发支持能力 15并发阻塞 1000并发稳定运行 并发能力大幅提升

4.2 线上运行稳定性验证

优化代码灰度上线7天,线上零BUG、零告警、零超时,接口响应持续稳定在180-220ms区间,数据库负载大幅下降,服务器资源利用率显著优化,完全达到生产可用标准。

同时缓存命中率稳定在96%,异步线程无堆积、无异常,所有业务逻辑与优化前完全一致,真正实现了"不改业务、只改性能"的极简重构

五、核心优化思想总结(可复用所有项目)

本次重构全程没有大规模改代码、没有重构架构、没有引入复杂中间件,仅通过5处极简代码改造,实现15倍性能提升。所有优化逻辑可完全复用在所有Java老旧项目中。

我总结出老旧Java接口性能优化的黄金优先级排序,99%的慢接口都可以按这个顺序优化,效率最高、风险最低:

1. 优先解决数据库IO问题(收益最大)

所有接口卡顿,80%都是数据库问题。优先排查N+1查询、全表扫描、select * 无效查询、无索引查询,这是性价比最高的优化方式,几行代码就能带来数倍性能提升。

2. 其次引入缓存消灭重复查询

针对更新频率低、查询频率高的静态数据,优先使用本地缓存(Caffeine),无需部署中间件、零运维成本,性能远超Redis,适合单机接口优化。

3. 非核心逻辑异步化,解放主流程

日志、统计、消息通知、积分更新等非核心逻辑,全部异步剥离,主流程只保留核心业务,大幅缩短响应时间。

4. 最后优化代码细节与配置

Gzip压缩、资源复用、杜绝重复计算、工具类优化等细节,积少成多,进一步压榨性能上限。

六、避坑指南:老旧项目优化千万不要踩的坑

本次优化过程中,我也规避了很多新手容易踩的坑,分享给大家,避免优化变事故:

6.1 禁止盲目大规模重构业务代码

祖传 项目代码 逻辑复杂、隐藏BUG多,大规模重构极易引发业务故障。优化核心原则:只改性能相关代码,不动业务逻辑,本次所有改造均是外层封装优化,核心业务代码零修改。

6.2 禁止滥用分布式缓存

很多人一上来就用Redis缓存,对于简单的静态数据,Redis的网络IO耗时反而高于查询数据库,得不偿失。优先本地缓存,热点数据、分布式场景再用Redis。

6.3 禁止使用默认异步线程池

CompletableFuture默认使用ForkJoinPool,会出现线程抢占、阻塞问题,业务异步必须自定义线程池,隔离线程资源,避免全局阻塞。

6.4 禁止盲目加索引

索引可以提升查询性能,但会降低新增、修改、删除性能,只针对高频查询字段加索引,杜绝冗余索引。

七、总结与感悟

很多开发者总觉得性能优化是高深、复杂、需要架构能力的高端操作,实则不然。对于90%的中小型公司、95%的老旧Java项目,性能卡顿根本不是架构瓶颈,而是开发者基础代码不规范、数据库操作野蛮、资源浪费导致的低级问题

本次祖传项目优化,全程仅修改百余行核心代码,无架构改造、无业务重构、无硬件扩容,就将接口响应从3秒砍至200毫秒,性能提升15倍,线上稳定运行零故障。

性能优化的核心真谛从来不是"写高深代码",而是杜绝无效消耗、精准定位瓶颈、极简高效改造。不用追求花哨的技术栈,把基础的代码规范、SQL优化、缓存思想、异步思想落地,就能解决绝大多数性能问题。

如果你也接手过卡顿严重的祖传Java项目,不用焦虑、不用重构整体代码,按照本文的排查思路+优化方案逐点改造,低成本、零风险实现接口性能暴涨。

后续我会持续分享更多老旧项目重构、Java性能优化、SQL调优实战干货,每一篇都是线上落地验证的真实案例,帮大家避开技术坑,提升实战能力。

相关推荐
Linsk1 小时前
组件 = 模板 + 业务逻辑
java·前端·vue.js
星沉远浦2 小时前
用Gemini高效解决Java代码报错难以定位的问题
java
用户298698530146 小时前
Word 文档字符级格式化:Java 实现方案详解
java·后端
笨鸟飞不快6 小时前
从单个服务到集群:一次完整的性能排查复盘
java·前端
荣码6 小时前
用Streamlit给AI应用套个界面,10行代码出Web页面
java·python
SamDeepThinking6 小时前
Java微服务练习方式
java·后端·微服务
朦胧之17 小时前
AI 编程-老项目改造篇
java·前端·后端
程序猿大帅1 天前
别再只当调包侠了:用 Spring AI 落地 Function Calling,我被大模型硬生生砸出了三个大坑
java
程序员晓琪1 天前
约定大于配置:基于 Java 包名自动生成 API 版本路由的最佳实践
java·spring boot·后端